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>
)
}
źródło
Ogólnie rzecz biorąc, użycie
setState
insideuseEffect
stworzy 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.useEffect
jest wywoływana po każdym renderowaniu, a kiedysetState
jest używana wewnątrz niego, spowoduje ponowne renderowanie komponentu, które będzie wywoływaćuseEffect
i tak dalej, i tak dalej.Jednym z popularnych przypadków, w których użycie
useState
inside ofuseEffect
nie spowoduje nieskończonej pętli, jest przekazanie pustej tablicy jako drugiego argumentu do funkcjiuseEffect
like,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.źródło
setState
wewnątrzuseEffect
jestsetting state
subskrypcja lub nasłuchiwanie wydarzeń. Ale nie zapomnij anulować subskrypcji reactjs.org/docs/hooks-effect.html#effects-with-cleanupW 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,
Comp
który otrzymujeprops
od rodzica i zgodnie zeprops
zmianą chcesz ustawićComp
stan. Z jakiegoś powodu musisz zmienić dla każdego rekwizytu innyuseEffect
: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.a
aprop.b
więc wartość,{...state}
kiedy to robisz,setState
jest dokładnie taka sama w obu,useEffect
ponieważ 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
setState
takie 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!
źródło
setName(name => ({ ...name, a: props.a }));
setItems(items => [...items, item])
useEffect
moż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 efektuNa przykład:
powyższy efekt zostanie uruchomiony dopiero po wyrenderowaniu komponentu. jest to podobne do
componentDidMount
cyklu życiaconst [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
componentDidUpdate
z tym, że nie każdy zmieniający się stan będzie go odpalał.Możesz przeczytać więcej szczegółów przez ten link
źródło
▶ 1. Czy mogę ustawić stan wewnątrz hooka useEffect?
Zasadniczo możesz dowolnie ustawiać stan tam, gdzie go potrzebujesz - w tym wewnątrz,
useEffect
a nawet podczas renderowania . Po prostu upewnij się, że unikasz nieskończonych pętli, odpowiednio ustawiając hookdeps
i / 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
:Pokaż fragment kodu
let MyComponent = () => { let [state, dispatch] = useReducer(reducer, { a: 1, b: 2 }); useEffect(() => { console.log("Some effect with B"); }, [state.b]); return ( <div> <p>A: {state.a}, B: {state.b}</p> <button onClick={() => dispatch({ type: "SET_A", payload: 5 })}> Set A to 5 and Check B </button> <button onClick={() => dispatch({ type: "INCREMENT_B" })}> Increment B </button> </div> ); }; // B depends on A. If B >= A, then reset B to 1. function reducer(state, { type, payload }) { const someCondition = state.b >= state.a; if (type === "SET_A") return someCondition ? { a: payload, b: 1 } : { ...state, a: payload }; else if (type === "INCREMENT_B") return { ...state, b: state.b + 1 }; return state; } ReactDOM.render(<MyComponent />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script> <div id="root"></div> <script>var { useReducer, useEffect } = React</script>
▶ 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?
useEffect
zawsze działa po zatwierdzeniu renderowania i zastosowaniu zmian DOM. Pierwszy efekt jest uruchamiany, zmienia sięb
i powoduje ponowne renderowanie. Po zakończeniu renderowania drugi efekt będzie działał ze względu nab
zmiany.Pokaż fragment kodu
let MyComponent = props => { console.log("render"); let [a, setA] = useState(1); let [b, setB] = useState(2); let isFirstRender = useRef(true); useEffect(() => { console.log("useEffect a, value:", a); if (isFirstRender.current) isFirstRender.current = false; else setB(3); return () => { console.log("unmount useEffect a, value:", a); }; }, [a]); useEffect(() => { console.log("useEffect b, value:", b); return () => { console.log("unmount useEffect b, value:", b); }; }, [b]); return ( <div> <p>a: {a}, b: {b}</p> <button onClick={() => { console.log("Clicked!"); setA(5); }} > click me </button> </div> ); }; ReactDOM.render(<MyComponent />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script> <div id="root"></div> <script>var { useReducer, useEffect, useState, useRef } = React</script>
▶ 4. Czy są jakieś wady wydajności w tworzeniu takiej struktury kodu?
Tak. Zawijając zmianę stanu
b
w osobnymuseEffect
fora
, przeglądarka ma dodatkową fazę layoutu / malowania - te efekty są potencjalnie widoczne dla użytkownika. Jeśli nie ma sposobu, abyuseReducer
spróbować, możesz zmienićb
stan razem za
bezpośrednio:Pokaż fragment kodu
let MyComponent = () => { console.log("render"); let [a, setA] = useState(1); let [b, setB] = useState(2); useEffect(() => { console.log("useEffect b, value:", b); return () => { console.log("unmount useEffect b, value:", b); }; }, [b]); const handleClick = () => { console.log("Clicked!"); setA(5); b >= 5 ? setB(1) : setB(b + 1); }; return ( <div> <p> a: {a}, b: {b} </p> <button onClick={handleClick}>click me</button> </div> ); }; ReactDOM.render(<MyComponent />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script> <div id="root"></div> <script>var { useReducer, useEffect, useState, useRef } = React</script>
źródło