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>
  )
}
Dan Ruswick
źródło

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ę

Shubham Khatri
źródło
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.

Hossam Mourad
źródło
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!

chociaż prawdziwe
źródło
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

Dogies007
źródło
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:

ford04
źródło