Czy Mogę ustawić stan wewnątrz Hooka useEffect
Powiedzmy, że mam pewien stan, który jest zależny od innego stanu(np. gdy a zmienia się, chcę, aby B zmienił).
Czy właściwe jest utworzenie Hooka, który obserwuje A i ustawia B wewnątrz Hooka useEffect?
Czy efekty będą kaskadowe tak, że po kliknięciu przycisku pierwszy efekt uruchomi się, powodując zmianę b, powodując uruchomienie drugiego efektu, przed następnym renderowaniem? Czy są jakieś minusy wydajności do strukturyzowania kodu w ten sposób?
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>
)
}
5 answers
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łania dopiero po zakończeniu fazy renderowania.
Powiedziawszy, że prawdopodobnie lepiej jest podjąć oba działania w tym samym efekcie, chyba że istnieje możliwość, że b
może się zmienić z powodów innych niż changing a
w którym to przypadku również chcesz wykonać tę samą logikę
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-06-11 13:52:11
Ogólnie rzecz biorąc, użycie setState
wewnątrz useEffect
stworzy nieskończoną pętlę, której najprawdopodobniej nie chcesz wywołać. Jest kilka wyjątków od tej zasady, o których później się dowiem.
useEffect
jest wywoływane po każdym renderowaniu i kiedy setState
jest używane wewnątrz niego, spowoduje to ponowne renderowanie komponentu, który wywoła useEffect
i tak dalej i tak dalej.
Jednym z popularnych przypadków, że użycie useState
wewnątrz useEffect
nie spowoduje nieskończonej pętli, jest przekazanie pustej tablicy jako drugi argument useEffect
Jak useEffect(() => {....}, [])
, co oznacza, że funkcja efektu powinna być wywołana raz: tylko po pierwszym montowaniu/renderowaniu. Jest to powszechnie używane, gdy wykonujesz pobieranie danych w komponencie i chcesz zapisać dane żądania w stanie komponentu.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-11-13 07:05:23
W przyszłości może to również pomóc:]}
Jest ok, aby używać setState w useEffect
wystarczy mieć uwagę, jak już opisano, aby nie tworzyć pętli.
Ale to nie jedyny problem, który może wystąpić. Zobacz poniżej:
Wyobraź sobie, że masz komponent Comp
, który odbiera props
od rodzica i zgodnie ze zmianą props
chcesz ustawić stan Comp
. Z jakiegoś powodu musisz zmienić dla każdego rekwizytu w innym useEffect
:
NIE RÓB To
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ć w 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 efekty działania są uruchamiane w tym samym cyklu Reacta, gdy zmienisz oba prop.a
i prop.b
, więc wartość {...state}
kiedy to zrobisz setState
są dokładnie takie same w obu useEffect
, ponieważ są w tym samym kontekście. Po uruchomieniu drugiego setState
zastąpi on pierwszy setState
.
DO THIS INSTEAD
Rozwiązaniem tego problemu jest w zasadzie wywołanie setState
w następujący sposób:
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 otrzymujesz najbardziej aktualną i poprawną wartość stanu, gdy kontynuujesz setState
.
Mam nadzieję, że to komuś pomoże!
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-08-23 08:48:07
useEffect
może zaczepić pewien rekwizyt lub stan. tak więc, rzeczą, którą musisz zrobić, aby uniknąć nieskończonej pętli hook, jest związanie jakiejś zmiennej lub stanu do efektu
Na Przykład:
useEffect(myeffectCallback, [])
Powyższy efekt zostanie wywołany tylko po wyrenderowaniu komponentu. jest to podobne do componentDidMount
cyklu życia
const [something, setSomething] = withState(0)
const [myState, setMyState] = withState(0)
useEffect(() => {
setSomething(0)
}, myState)
Powyższy efekt odpali tylko Mój stan się zmienił to jest podobne do componentDidUpdate
tyle że nie każdy zmieniający się stan odpali go.
Możesz przeczytać więcej szczegółów, chociaż ten link
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-08-23 00:38:44
▶ 1. Czy Mogę ustawić stan wewnątrz Hooka useEffect?
W zasadzie można dowolnie ustawić stan tam, gdzie jest to potrzebne - w tym wewnątrz useEffect
i nawet podczas renderowania . Upewnij się, że unikasz nieskończonych pętli, ustawiając Hook deps
poprawnie i / lub warunkowo.
▶ 2. Powiedzmy, że mam jakiś stan, który jest zależny od innego stanu. Czy właściwe jest utworzenie Hooka, który obserwuje A i ustawia B wewnątrz Hooka useEffect?
Ty po prostu opisany klasyczny przypadek użycia dla useReducer
:
useReducer
jest zwykle korzystniejszy oduseState
, gdy masz złożony stan logika, która zawiera wiele pod-wartości lub gdy następny stan zależy od poprzedniego. ( react docs )Podczas ustawiania zmiennej state zależy od bieżącej wartości innego stanu , Możesz spróbować zastąpić je obie zmienną
useReducer
. [...] Kiedy znajdziesz siebie piszącsetSomething(something => ...)
, to dobry moment, aby rozważyć użycie reduktora. (Dan Abramov, Overreacted blog )
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ą kaskadowe w taki sposób, że po kliknięciu przycisku pierwszy efekt uruchomi się, powodując zmianę b, powodując uruchomienie drugiego efektu, przed następnym renderowaniem?
useEffect
zawsze uruchamia Po Render jest zatwierdzany i stosowane są zmiany DOM. Pierwszy efekt jest wywołany, zmienia b
i powoduje ponowne renderowanie. Po zakończeniu renderowania, Drugi efekt zostanie uruchomiony z powodu zmian b
.
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ś minusy wydajności do strukturyzowania kodu w ten sposób?
Tak. Poprzez owinięcie zmiany stanub
w oddzielny useEffect
dla a
, przeglądarka ma dodatkową fazę layout/paint - efekty te są potencjalnie widoczne dla użytkownika. Jeśli nie ma sposobu, aby dać useReducer
spróbować, możesz zmienić b
Stan razem z a
bezpośrednio:
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>
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-03-26 12:58:08