Jaki jest pożytek ze składni funkcjonalnej setState w komponentach funkcyjnych reagowania? [duplikować]

Dec 13 2020

mówimy o posiadaniu elementów funkcjonalnych useState

powiedzmy

const [age, setAge] = useState(0)

teraz powiedzmy, że podczas aktualizacji agemuszę użyć poprzedniejage

Dokumenty React wspominają o czymś, co nazywa się FUNKCJONALNE AKTUALIZACJE, gdzie można przekazać funkcję, a argumentem do niej będzie poprzednia wartość stanu, np.

setState((previousAge) => previousAge + 1)

dlaczego muszę to robić, skoro mogę to zrobić

setState(previousAge + 1)

jakie korzyści płyną z używania funkcjonalności setState,

Wiem, że w komponentach opartych na klasach było coś, co nazywa się grupowaniem aktualizacji stanu w sposób funkcjonalny, ale nie mogę znaleźć czegoś takiego w dokumentacji komponentów funkcjonalnych.

Odpowiedzi

2 ehab Dec 14 2020 at 01:38

Nie są takie same, jeśli twoja aktualizacja zależy od poprzedniej wartości znalezionej w stanie, powinieneś użyć formularza funkcjonalnego. Jeśli w tym przypadku nie użyjesz formularza funkcjonalnego, kod czasami się zepsuje.

Dlaczego się psuje i kiedy

Komponenty funkcjonalne React to po prostu zamknięcia, wartość stanu, którą masz w zamknięciu, może być nieaktualna - oznacza to, że wartość wewnątrz zamknięcia nie odpowiada wartości, która jest w stanie React dla tego komponentu, może się to zdarzyć w następujące przypadki:

1- operacje asynchroniczne (w tym przykładzie kliknij powolne dodawanie, a następnie kliknij kilka razy przycisk dodawania, później zobaczysz, że stan został zresetowany do tego, co znajdowało się wewnątrz zamknięcia, gdy kliknięto przycisk powolnego dodawania)

const App = () => {
  const [counter, setCounter] = useState(0);

  return (
    <>
      <p>counter {counter} </p>
      <button
        onClick={() => {
          setCounter(counter + 1);
        }}
      >
        immediately add
      </button>
      <button
        onClick={() => {
          setTimeout(() => setCounter(counter + 1), 1000);
        }}
      >
        Add
      </button>
    </>
  );
};

2- W przypadku wielokrotnego wywoływania funkcji aktualizacji w tym samym zamknięciu

const App = () => {
  const [counter, setCounter] = useState(0);

  return (
    <>
      <p>counter {counter} </p>
      <button
        onClick={() => {
          setCounter(counter + 1);
          setCounter(counter + 1);
        }}
      >
        Add twice
      </button>
   
    </>
  );
}
1 HeroWanders Dec 13 2020 at 23:59

Mogą wystąpić problemy w zależności od tego, jak szybko / często wywoływany jest seter.

Jeśli używasz prostego sposobu, pobierając wartość z zamknięcia, kolejne wywołania między dwoma renderami mogą nie przynieść pożądanego efektu.

Prosty przykład:

function App() {
    const [counter, setCounter] = useState(0);
    const incWithClosure = () => {
        setCounter(counter + 1);
    };
    const incWithUpdate = () => {
        setCounter(oldCounter => oldCounter + 1);
    };

    return (<>
        <button onClick={_ => { incWithClosure(); incWithClosure(); }}>
            Increment twice using incWithClosure
        </button>
        <button onClick={_ => { incWithUpdate(); incWithUpdate(); }}>
            Increment twice using incWithUpdate
        </button>
        <p>{counter}</p>
    </>);
}

Oba przyciski dwukrotnie wywołują jedną z metod inkrementacji. Ale obserwujemy:

  • Pierwszy przycisk zwiększy licznik tylko o 1
  • Drugi przycisk zwiększy licznik o 2, co jest prawdopodobnie pożądanym wynikiem.

Kiedy to się może stać?

  • Oczywiście, jeśli incWithClosurejest wywoływana wiele razy bezpośrednio po sobie
  • Jeśli zaangażowane są zadania asynchroniczne, może się to łatwo zdarzyć (patrz poniżej)
  • Być może, jeśli React ma dużo do zrobienia, jego algorytmy planowania mogą zdecydować o obsłudze wielu bardzo szybkich kliknięć za pomocą tego samego modułu obsługi zdarzeń

Przykład z pracą asynchroniczną (symulacja ładowania zasobu):

function App() {
  const [counter, setCounter] = useState(0);
  const incWithClosureDelayed = () => {
    setTimeout(() => {
      setCounter(counter + 1);
    }, 1000);
  };
  const incWithUpdateDelayed = () => {
    setTimeout(() => {
      setCounter((oldCounter) => oldCounter + 1);
    }, 1000);
  };

  return (
    <>
      <button onClick={(_) => incWithClosureDelayed()}>
        Increment slowly using incWithClosure
      </button>
      <button onClick={(_) => incWithUpdateDelayed()}>
        Increment slowly using incWithUpdate
      </button>
      <p>{counter}</p>
    </>
  );
}

Kliknij dwukrotnie pierwszy przycisk (w ciągu jednej sekundy) i obserwuj, że licznik jest zwiększany tylko o 1. Drugi przycisk zachowuje się poprawnie.

slebetman Dec 13 2020 at 23:26

Ponieważ jeśli tego nie zrobisz , w pewnym momencie okaże się, że otrzymasz starą wartość age. Problem w tym, że czasami to, co sugerujesz, zadziała. Ale czasami tak nie jest. Może nie zepsuć się w obecnym kodzie dzisiaj, ale może się zepsuć w innym kodzie napisanym kilka tygodni temu lub w obecnym kodzie za kilka miesięcy.

Objaw jest naprawdę, naprawdę dziwny. Możesz wydrukować wartość zmiennej wewnątrz komponentu jsx, używając {x}składni, a później wydrukować tę samą zmienną, używając console.log po wyrenderowaniu komponentu jsx (nie wcześniej) i stwierdzić, że console.logwartość jest nieaktualna - to, console.logco dzieje się po renderowaniu, może w jakiś sposób mieć starszą wartość niż renderowanie.

Zatem rzeczywista wartość zmiennych stanu może nie zawsze działać poprawnie w zwykłym kodzie - są one przeznaczone tylko do zwracania najnowszej wartości w renderowaniu. Z tego powodu zaimplementowano mechanizm wywołania zwrotnego w programie ustawiającym stan, aby umożliwić uzyskanie najnowszej wartości zmiennej stanu w zwykłym kodzie poza renderowaniem.