Czy mogę ustawić stan wewnątrz haka useEffect


124

Powiedzmy, że mam jakiś stan, który jest zależny od innego stanu (np. Kiedy A się zmienia, chcę zmienić B).

Czy właściwe jest utworzenie haka, który obserwuje A i ustawia B wewnątrz haka useEffect?

Czy efekty będą kaskadowe w taki sposób, że po kliknięciu przycisku pierwszy efekt zostanie uruchomiony, powodując zmianę b, powodując uruchomienie drugiego efektu przed kolejnym renderowaniem? Czy są jakieś wady wydajności w tworzeniu takiej struktury kodu?

let MyComponent = props => {
  let [a, setA] = useState(1)
  let [b, setB] = useState(2)
  useEffect(
    () => {
      if (/*some stuff is true*/) {
        setB(3)
      }
    },
    [a],
  )
  useEffect(
    () => {
      // do some stuff
    },
    [b],
  )

  return (
    <button
      onClick={() => {
        setA(5)
      }}
    >
      click me
    </button>
  )
}

Odpowiedzi:


31

Efekty są zawsze wykonywane po zakończeniu fazy renderowania, nawet jeśli ustawisz Stan wewnątrz jednego efektu, inny efekt odczyta zaktualizowany stan i podejmie na nim działanie dopiero po fazie renderowania.

Powiedziawszy, że prawdopodobnie lepiej jest wykonać obie czynności w tym samym efekcie, chyba że istnieje możliwość, że bmoże się to zmienić z powodów innych niż changing aw którym to przypadku również chciałbyś wykonać tę samą logikę


4
Więc jeśli A zmieni B, komponent wyrenderuje się dwukrotnie, prawda?
Dan Ruswick

2
Chcę też poznać odpowiedź na powyższe pytanie
alaboudi

2
@alaboudi Tak, jeśli A zmienia powodując useeffect do biegu, który ustawia B następnie składnik nie czynią dwukrotnie
Shubham Khatri

@alaboudi Yes .. jak powiedział Shubham Khatri, będzie on ponownie renderowany. ale możesz pominąć wywoływanie efektu po ponownym renderowaniu, używając drugiego argumentu referjs.org/docs/…
Karthikeyan

108

Ogólnie rzecz biorąc, użycie setStateinside useEffectstworzy nieskończoną pętlę, której najprawdopodobniej nie chcesz wywołać. Jest kilka wyjątków od tej reguły, o których później powiem.

useEffectjest wywoływana po każdym renderowaniu, a kiedy setStatejest używana wewnątrz niego, spowoduje ponowne renderowanie komponentu, które będzie wywoływać useEffecti tak dalej, i tak dalej.

Jednym z popularnych przypadków, w których użycie useStateinside of useEffectnie spowoduje nieskończonej pętli, jest przekazanie pustej tablicy jako drugiego argumentu do funkcji useEffectlike, useEffect(() => {....}, [])co oznacza, że ​​funkcję efektu należy wywołać raz: tylko po pierwszym zamontowaniu / renderowaniu. Jest to szeroko stosowane, gdy wykonujesz pobieranie danych w komponencie i chcesz zapisać dane żądania w stanie komponentu.


3
Innym przypadkiem do użycia setStatewewnątrz useEffectjest setting statesubskrypcja lub nasłuchiwanie wydarzeń. Ale nie zapomnij anulować subskrypcji reactjs.org/docs/hooks-effect.html#effects-with-cleanup
timqian

35
Nie jest to do końca prawdą - useState uruchamia się tylko wtedy, gdy wartość, którą aktualizujesz stan, różni się od poprzedniej, więc nieskończona pętla jest zabroniona, chyba że wartość zmienia się między cyklami.
RobM

14
Ta odpowiedź jest niepoprawna i nie do sedna pytania: w przypadku kodu komponent renderuje się tylko dwa razy po kliknięciu przycisku, nie ma nieskończonej pętli
Bogdan D

15
Bardzo częstym przypadkiem użycia jest ustawienie stanu wewnątrz elementu useEffect. Pomyśl o ładowaniu danych, useEffect wywołuje API, pobiera dane, ustawia za pomocą zestawu części useState.
badbod99

3
Zaktualizowałem odpowiedź, aby rozwiązać wspomniane przez Ciebie problemy. Czy teraz jest lepiej?
Hossam Mourad

58

W przyszłości może to również pomóc:

Używanie setState jest w porządku useEffect, wystarczy zwrócić uwagę, jak już opisano, aby nie tworzyć pętli.

Ale to nie jedyny problem, jaki może się pojawić. Zobacz poniżej:

Wyobraź sobie, że masz komponent, Compktóry otrzymuje propsod rodzica i zgodnie ze propszmianą chcesz ustawić Compstan. Z jakiegoś powodu musisz zmienić dla każdego rekwizytu inny useEffect:

NIE RÓB TEGO

useEffect(() => {
  setState({ ...state, a: props.a });
}, [props.a]);

useEffect(() => {
  setState({ ...state, b: props.b });
}, [props.b]);

Może nigdy nie zmienić stanu a, jak widać na tym przykładzie: https://codesandbox.io/s/confident-lederberg-dtx7w

Powodem, dla którego tak się dzieje w tym przykładzie, jest to, że oba useEffects działają w tym samym cyklu reakcji, gdy zmieniasz oba, prop.aa prop.bwięc wartość, {...state}kiedy to robisz, setStatejest dokładnie taka sama w obu, useEffectponieważ znajdują się w tym samym kontekście. Kiedy uruchomisz drugą setState, zastąpi ona pierwszą setState.

ZRÓB TO ZAMIAST

Rozwiązanie tego problemu jest w zasadzie setStatetakie wywołanie :

useEffect(() => {
  setState(state => ({ ...state, a: props.a }));
}, [props.a]);

useEffect(() => {
  setState(state => ({ ...state, b: props.b }));
}, [props.b]);

Sprawdź rozwiązanie tutaj: https://codesandbox.io/s/mutable-surf-nynlx

Teraz zawsze otrzymasz najbardziej aktualną i poprawną wartość stanu, gdy będziesz kontynuować setState.

Mam nadzieję, że to komuś pomoże!


3
powyższe rozwiązanie pomogło misetName(name => ({ ...name, a: props.a }));
mufaddal_mw

2
pomogło mi to również w części z funkcją strzałeksetItems(items => [...items, item])
Stefan Zhelyazkov

Wow, jesteś niesamowity. Uratowałeś moje a * s dzisiaj.
Pratik Soni

Spędziłem od 7 rano do 3 po południu bez rozwiązania, a teraz mnie uratowałeś.
Dinindu Kanchana

24

useEffectmoże zaczepić o określony rekwizyt lub stan. więc rzeczą, którą musisz zrobić, aby uniknąć przechwytywania nieskończonej pętli, jest przypisanie jakiejś zmiennej lub stanu do efektu

Na przykład:

useEffect(myeffectCallback, [])

powyższy efekt zostanie uruchomiony dopiero po wyrenderowaniu komponentu. jest to podobne do componentDidMountcyklu życia

const [something, setSomething] = withState(0)
const [myState, setMyState] = withState(0)
useEffect(() => {
  setSomething(0)
}, myState)

Powyższy efekt będzie odpalać tylko mój stan się zmienił jest to podobne do componentDidUpdatez tym, że nie każdy zmieniający się stan będzie go odpalał.

Możesz przeczytać więcej szczegółów przez ten link


1
Dziękuję, ta odpowiedź odnosi się do tablicy zależności useEffect w sposób, w jaki inna odpowiedź nie. Dołączenie pustej tablicy jako drugiego argumentu funkcji useEffect zapewni wykonanie metody useEffect po wyrenderowaniu składnika, ale dołączenie tablicy o określonym stanie lub określonych stanach spowoduje wykonanie metody useEffect po zmianie stanów w odwołaniu.
adriaanbd

11

▶ 1. Czy mogę ustawić stan wewnątrz hooka useEffect?

Zasadniczo możesz dowolnie ustawiać stan tam, gdzie go potrzebujesz - w tym wewnątrz, useEffecta nawet podczas renderowania . Po prostu upewnij się, że unikasz nieskończonych pętli, odpowiednio ustawiając hook depsi / lub warunkowo.


▶ 2. Powiedzmy, że mam stan, który jest zależny od innego stanu. Czy właściwe jest utworzenie haka, który obserwuje A i ustawia B wewnątrz haka useEffect?

Po prostu opisane w klasyczny przypadek użycia dla useReducer:

useReducerjest zwykle lepsze niż useStatewtedy, gdy masz złożoną logikę stanu, która obejmuje wiele wartości podrzędnych lub gdy następny stan zależy od poprzedniego. ( Reaguj dokumenty )

Gdy ustawienie zmiennej stanu zależy od bieżącej wartości innej zmiennej stanu , możesz spróbować zastąpić je obie zmiennymi useReducer. [...] Kiedy zaczynasz pisać setSomething(something => ...) , to dobry moment, aby rozważyć użycie zamiast tego reduktora. ( Dan Abramov, Przereagowany blog )


▶ 3. Czy efekty będą się kaskadować w taki sposób, że po kliknięciu przycisku pierwszy efekt zostanie uruchomiony, powodując zmianę b, powodując uruchomienie drugiego efektu przed kolejnym renderowaniem?

useEffectzawsze działa po zatwierdzeniu renderowania i zastosowaniu zmian DOM. Pierwszy efekt jest uruchamiany, zmienia się bi powoduje ponowne renderowanie. Po zakończeniu renderowania drugi efekt będzie działał ze względu na bzmiany.


▶ 4. Czy są jakieś wady wydajności w tworzeniu takiej struktury kodu?

Tak. Zawijając zmianę stanu bw osobnym useEffectfor a, przeglądarka ma dodatkową fazę layoutu / malowania - te efekty są potencjalnie widoczne dla użytkownika. Jeśli nie ma sposobu, aby useReducerspróbować, możesz zmienić bstan razem z abezpośrednio:

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.