React hooks wprowadza useState
do ustawiania stanu komponentu. Ale jak mogę użyć hooków, aby zamienić wywołanie zwrotne, jak poniżej:
setState(
{ name: "Michael" },
() => console.log(this.state)
);
Chcę coś zrobić po zaktualizowaniu stanu.
Wiem, że mogę useEffect
zrobić dodatkowe rzeczy, ale muszę sprawdzić poprzednią wartość stanu, która wymaga kodu bitowego. Szukam prostego rozwiązania, które można zastosować z useState
hakiem.
Odpowiedzi:
Aby
useEffect
to osiągnąć, musisz użyć haka.const [counter, setCounter] = useState(0); const doSomething = () => { setCounter(123); } useEffect(() => { console.log('Do something after counter has changed', counter); }, [counter]);
źródło
console.log
przy pierwszym renderowaniu, a także przy każdejcounter
zmianie czasu . A co, jeśli chcesz coś zrobić dopiero po zaktualizowaniu stanu, ale nie przy pierwszym renderowaniu, gdy ustawiona jest wartość początkowa? Myślę, że możesz sprawdzić wartośćuseEffect
i zdecydować, czy chcesz wtedy coś zrobić. Czy byłoby to uznane za najlepszą praktykę?useEffect
przy początkowym renderowaniu, możesz utworzyć niestandardowyuseEffect
punkt zaczepienia , który nie jest uruchamiany podczas początkowego renderowania. Aby stworzyć taki hook, możesz sprawdzić to pytanie: stackoverflow.com/questions/53253940/…Warning: State updates from the useState() and useReducer() Hooks don't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect().
Jeśli chcesz zaktualizować poprzedni stan, możesz to zrobić w hookach:
const [count, setCount] = useState(0); setCount(previousCount => previousCount + 1);
źródło
setCounter
masz na myślisetCount
expected as assignment or function call and instead saw an expression
useEffect
, który uruchamia się tylko przy aktualizacjach stanu :const [state, setState] = useState({ name: "Michael" }) const isFirstRender = useRef(true) useEffect(() => { if (!isFirstRender.current) { console.log(state) // do something after state has updated } }, [state]) useEffect(() => { isFirstRender.current = false // toggle flag after first render/mounting }, [])
Powyższe będzie
setState
najlepiej naśladować wywołanie zwrotne i nie będzie wywoływać stanu początkowego. Możesz wyodrębnić z niego niestandardowy hook:function useEffectUpdate(effect, deps) { const isFirstRender = useRef(true) useEffect(() => { if (!isFirstRender.current) { effect() } }, deps) // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { isFirstRender.current = false; }, []); } // ... Usage inside component useEffectUpdate(() => { console.log(state) }, [state])
źródło
useState
może być zaimplementowana w celu otrzymania wywołania zwrotnego jaksetState
w klasach.Myślę, że używanie
useEffect
nie jest intuicyjnym sposobem.Stworzyłem do tego opakowanie. W tym niestandardowym podpięciu możesz przesłać wywołanie zwrotne do
setState
parametru zamiastuseState
parametru.Właśnie stworzyłem wersję Typescript. Więc jeśli chcesz użyć tego w JavaScript, po prostu usuń jakąś notację typu z kodu.
Stosowanie
const [state, setState] = useStateCallback(1); setState(2, (n) => { console.log(n) // 2 });
Deklaracja
import { SetStateAction, useCallback, useEffect, useRef, useState } from 'react'; type Callback<T> = (value?: T) => void; type DispatchWithCallback<T> = (value: T, callback?: Callback<T>) => void; function useStateCallback<T>(initialState: T | (() => T)): [T, DispatchWithCallback<SetStateAction<T>>] { const [state, _setState] = useState(initialState); const callbackRef = useRef<Callback<T>>(); const isFirstCallbackCall = useRef<boolean>(true); const setState = useCallback((setStateAction: SetStateAction<T>, callback?: Callback<T>): void => { callbackRef.current = callback; _setState(setStateAction); }, []); useEffect(() => { if (isFirstCallbackCall.current) { isFirstCallbackCall.current = false; return; } callbackRef.current?.(state); }, [state]); return [state, setState]; } export default useStateCallback;
Wada
Jeśli przekazana funkcja strzałki odwołuje się do zmiennej zewnętrznej funkcji, to po zaktualizowaniu stanu przechwyci bieżącą wartość, a nie wartość. W powyższym przykładzie użycia console.log (stan) wyświetli 1, a nie 2.
źródło
state
odnosi się do poprzedniego, gdy wywoływany jest callback. Prawda?setState()
umieszcza w kolejce zmiany stanu składnika i informuje Reacta, że ten składnik i jego elementy potomne muszą zostać ponownie wyrenderowane ze zaktualizowanym stanem.setState jest asynchroniczna i właściwie nie zwraca obietnicy. Więc w przypadkach, gdy chcemy zaktualizować lub wywołać funkcję, funkcję tę można nazwać callback w funkcji setState jako drugi argument. Na przykład w powyższym przypadku wywołałeś funkcję jako wywołanie zwrotne setState.
setState( { name: "Michael" }, () => console.log(this.state) );
Powyższy kod działa dobrze dla komponentu klasy, ale w przypadku komponentu funkcjonalnego nie możemy użyć metody setState, a to możemy wykorzystać hook efektu użycia, aby osiągnąć ten sam wynik.
Oczywistą metodą, która przychodzi na myśl, jest to, że ypu może używać z useEffect, jak poniżej:
const [state, setState] = useState({ name: "Michael" }) useEffect(() => { console.log(state) // do something after state has updated }, [state])
Ale uruchomiłoby się to również przy pierwszym renderowaniu, więc możemy zmienić kod w następujący sposób, w którym możemy sprawdzić pierwsze zdarzenie renderowania i uniknąć renderowania stanu. Dlatego wdrożenie można przeprowadzić w następujący sposób:
Możemy tutaj użyć haka użytkownika, aby zidentyfikować pierwszy render.
Hook useRef pozwala nam tworzyć zmienne zmienne w komponentach funkcjonalnych. Przydaje się do uzyskiwania dostępu do węzłów DOM / elementów React i do przechowywania zmiennych zmiennych bez wyzwalania ponownego renderowania.
const [state, setState] = useState({ name: "Michael" }); const firstTimeRender = useRef(true); useEffect(() => { if (!firstTimeRender.current) { console.log(state); } }, [state]) useEffect(() => { firstTimeRender.current = false }, [])
źródło
Miałem ten sam problem, użycie useEffect w mojej konfiguracji nie załatwiło sprawy (aktualizuję stan rodzica z tablicy wielu komponentów podrzędnych i muszę wiedzieć, który składnik zaktualizował dane).
Zawijanie setState w obietnicy umożliwia wywołanie dowolnej akcji po zakończeniu:
import React, {useState} from 'react' function App() { const [count, setCount] = useState(0) function handleClick(){ Promise.resolve() .then(() => { setCount(count => count+1)}) .then(() => console.log(count)) } return ( <button onClick= {handleClick}> Increase counter </button> ) } export default App;
Następujące pytanie skierowało mnie we właściwym kierunku: Czy aktualizacja stanu wsadowego React działa podczas korzystania z zaczepów?
źródło
Twoje pytanie jest bardzo trafne, powiem ci, że useEffect jest uruchamiany domyślnie raz i po każdej zmianie tablicy zależności.
sprawdź poniższy przykład:
import React,{ useEffect, useState } from "react"; const App = () => { const [age, setAge] = useState(0); const [ageFlag, setAgeFlag] = useState(false); const updateAge = ()=>{ setAgeFlag(false); setAge(age+1); setAgeFlag(true); }; useEffect(() => { if(!ageFlag){ console.log('effect called without change - by default'); } else{ console.log('effect called with change '); } }, [ageFlag,age]); return ( <form> <h2>hooks demo effect.....</h2> {age} <button onClick={updateAge}>Text</button> </form> ); } export default App;
Jeśli chcesz, aby wywołanie zwrotne setState było wykonywane z zaczepami, użyj zmiennej flag i podaj blok IF ELSE LUB IF wewnątrz useEffect, tak aby po spełnieniu tych warunków wykonywany był tylko ten blok kodu. Jakkolwiek czas działa, gdy zmienia się tablica zależności, ale ten kod IF wewnątrz efektu będzie wykonywany tylko w tych określonych warunkach.
źródło
Miałem przypadek użycia, w którym chciałem wykonać wywołanie interfejsu API z kilkoma parametrami po ustawieniu stanu. Nie chciałem ustawiać tych parametrów jako swojego stanu, więc zrobiłem niestandardowy hak i oto moje rozwiązanie
import { useState, useCallback, useRef, useEffect } from 'react'; import _isFunction from 'lodash/isFunction'; import _noop from 'lodash/noop'; export const useStateWithCallback = initialState => { const [state, setState] = useState(initialState); const callbackRef = useRef(_noop); const handleStateChange = useCallback((updatedState, callback) => { setState(updatedState); if (_isFunction(callback)) callbackRef.current = callback; }, []); useEffect(() => { callbackRef.current(); callbackRef.current = _noop; // to clear the callback after it is executed }, [state]); return [state, handleStateChange]; };
źródło
UseEffect jest podstawowym rozwiązaniem. Ale jak wspomniał Darryl, używając useEffect i przekazując stan, ponieważ drugi parametr ma jedną wadę, komponent będzie działał w procesie inicjalizacji. Jeśli chcesz, aby funkcja zwrotna działała z wartością zaktualizowanego stanu, możesz ustawić stałą lokalną i użyć jej zarówno w funkcji setState, jak i callback.
const [counter, setCounter] = useState(0); const doSomething = () => { const updatedNumber = 123; setCounter(updatedNumber); // now you can "do something" with updatedNumber and don't have to worry about the async nature of setState! console.log(updatedNumber); }
źródło