Wypróbowuję nowe haki do reagowania i mam komponent zegara z licznikiem, który ma zwiększać się co sekundę. Jednak wartość nie przekracza jednego.
function Clock() {
const [time, setTime] = React.useState(0);
React.useEffect(() => {
const timer = window.setInterval(() => {
setTime(time + 1);
}, 1000);
return () => {
window.clearInterval(timer);
};
}, []);
return (
<div>Seconds: {time}</div>
);
}
ReactDOM.render(<Clock />, document.querySelector('#app'));
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>
<div id="app"></div>
źródło
useEffect
funkcja jest oceniana tylko raz podczas montowania komponentu, gdy podana jest pusta lista wejść.Alternatywą
setInterval
jest ustawienie nowego interwału przysetTimeout
każdej aktualizacji stanu:const [time, setTime] = React.useState(0); React.useEffect(() => { const timer = setTimeout(() => { setTime(time + 1); }, 1000); return () => { clearTimeout(timer); }; }, [time]);
Wpływ na wydajność
setTimeout
jest nieznaczny i ogólnie można go zignorować. O ile komponent nie jest wrażliwy na czas do momentu, w którym nowo ustawione limity czasu powodują niepożądane skutki, oba podejściasetInterval
isetTimeout
podejścia są dopuszczalne.źródło
Jak zauważyli inni, problem polega na tym, że
useState
jest wywoływana tylko raz (asdeps = []
), aby ustawić interwał:React.useEffect(() => { const timer = window.setInterval(() => { setTime(time + 1); }, 1000); return () => window.clearInterval(timer); }, []);
Następnie, za każdym razem
setInterval
, gdy tyknie, faktycznie zadzwonisetTime(time + 1)
, aletime
zawsze zachowa wartość, którą miał początkowo, gdysetInterval
zostało zdefiniowane wywołanie zwrotne (zamknięcie).Możesz użyć alternatywnej formy
useState
setera i podać wywołanie zwrotne zamiast rzeczywistej wartości, którą chcesz ustawić (tak jak w przypadkusetState
):setTime(prevTime => prevTime + 1);
Ale zachęcałbym cię do stworzenia własnego
useInterval
hooka, abyś mógł wysuszyć i uprościć swój kod używającsetInterval
deklaratywnie , jak sugeruje Dan Abramov tutaj w Making setInterval Declarative with React Hooks :function useInterval(callback, delay) { const intervalRef = React.useRef(); const callbackRef = React.useRef(callback); // Remember the latest callback: // // Without this, if you change the callback, when setInterval ticks again, it // will still call your old callback. // // If you add `callback` to useEffect's deps, it will work fine but the // interval will be reset. React.useEffect(() => { callbackRef.current = callback; }, [callback]); // Set up the interval: React.useEffect(() => { if (typeof delay === 'number') { intervalRef.current = window.setInterval(() => callbackRef.current(), delay); // Clear interval if the components is unmounted or the delay changes: return () => window.clearInterval(intervalRef.current); } }, [delay]); // Returns a ref to the interval ID in case you want to clear it manually: return intervalRef; } const Clock = () => { const [time, setTime] = React.useState(0); const [isPaused, setPaused] = React.useState(false); const intervalRef = useInterval(() => { if (time < 10) { setTime(time + 1); } else { window.clearInterval(intervalRef.current); } }, isPaused ? null : 1000); return (<React.Fragment> <button onClick={ () => setPaused(prevIsPaused => !prevIsPaused) } disabled={ time === 10 }> { isPaused ? 'RESUME ⏳' : 'PAUSE 🚧' } </button> <p>{ time.toString().padStart(2, '0') }/10 sec.</p> <p>setInterval { time === 10 ? 'stopped.' : 'running...' }</p> </React.Fragment>); } ReactDOM.render(<Clock />, document.querySelector('#app'));
body, button { font-family: monospace; } body, p { margin: 0; } p + p { margin-top: 8px; } #app { display: flex; flex-direction: column; align-items: center; min-height: 100vh; } button { margin: 32px 0; padding: 8px; border: 2px solid black; background: transparent; cursor: pointer; border-radius: 2px; }
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script> <script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script> <div id="app"></div>
Oprócz tworzenia prostszego i czystszego kodu, pozwala to na automatyczne wstrzymanie (i wyczyszczenie) interwału, po prostu przekazując go,
delay = null
a także zwraca identyfikator interwału, na wypadek, gdybyś chciał anulować go ręcznie (nie jest to uwzględnione w postach Dana).Właściwie można to również poprawić, aby nie uruchamiało się ponownie
delay
po wznowieniu, ale myślę, że w większości przypadków jest to wystarczająco dobre.Jeśli szukasz podobnej odpowiedzi
setTimeout
zamiastsetInterval
, sprawdź to: https://stackoverflow.com/a/59274757/3723993 .Można również znaleźć deklaratywny wersję
setTimeout
asetInterval
,useTimeout
auseInterval
, a także zwyczajuseThrottledCallback
hak napisany na maszynie w https://gist.github.com/Danziger/336e75b6675223ad805a88c2dfdcfd4a .źródło
Alternatywnym rozwiązaniem byłoby użycie
useReducer
, gdyż zawsze zostanie przekazany stan obecny.function Clock() { const [time, dispatch] = React.useReducer((state = 0, action) => { if (action.type === 'add') return state + 1 return state }); React.useEffect(() => { const timer = window.setInterval(() => { dispatch({ type: 'add' }); }, 1000); return () => { window.clearInterval(timer); }; }, []); return ( <div>Seconds: {time}</div> ); } ReactDOM.render(<Clock />, document.querySelector('#app'));
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script> <script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script> <div id="app"></div>
źródło
useEffect
tutaj jest wywoływana wiele razy, aby zaktualizować czas, podczas gdy tablica zależności jest pusta, co oznacza, żeuseEffect
powinna być wywoływana tylko przy pierwszym renderowaniu komponentu / aplikacji?useEffect
jest wywoływana tylko raz, kiedy komponent rzeczywiście się wyrenderuje . Ale w środkusetInterval
jest odpowiedzialny za regularną zmianę czasu. Proponuję trochę poczytaćsetInterval
, po tym wszystko powinno być jaśniejsze! developer.mozilla.org/en-US/docs/Web/API/ ...Zrób tak, jak poniżej, działa dobrze.
const [count , setCount] = useState(0); async function increment(count,value) { await setCount(count => count + 1); } //call increment function increment(count);
źródło
count => count + 1
oddzwonienie zadziałało dla mnie, dzięki!To rozwiązanie nie działa dla mnie, ponieważ muszę pobrać zmienną i zrobić kilka rzeczy, a nie tylko ją zaktualizować.
Otrzymuję obejście, aby uzyskać zaktualizowaną wartość zaczepu z obietnicą
Na przykład:
async function getCurrentHookValue(setHookFunction) { return new Promise((resolve) => { setHookFunction(prev => { resolve(prev) return prev; }) }) }
Dzięki temu mogę uzyskać wartość wewnątrz funkcji setInterval w ten sposób
let dateFrom = await getCurrentHackValue(setSelectedDateFrom);
źródło
Poinformuj Reacta o ponownym renderowaniu, gdy czas się zmieni zrezygnować
function Clock() { const [time, setTime] = React.useState(0); React.useEffect(() => { const timer = window.setInterval(() => { setTime(time + 1); }, 1000); return () => { window.clearInterval(timer); }; }, [time]); return ( <div>Seconds: {time}</div> ); } ReactDOM.render(<Clock />, document.querySelector('#app'));
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script> <script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script> <div id="app"></div>
źródło
count
zmianie.setTimeout()
jest preferowane, jak wskazał Estus