let updateTimer: number; export function Timer() { const [count, setCount] = React.useState<number>(0); const [messages, setMessages] = React.useState<string[]>([]); const start = () => { updateTimer = setInterval(() => { const m = [...messages]; m.push("called"); setMessages(m); setCount(count + 1); }, 1000); }; const stop = () => { clearInterval(updateTimer); }; return ( <> <div>{count}</div> <button onClick={start}>Start</button> <button onClick={stop}>Stop</button> {messages.map((message, i) => ( <p key={i}>{message}</p> ))} </> ); }
代码示例:https://codesandbox.io/s/romantic-wing-9yxw8 ?file=/src/App.tsx
代码有两个按钮——开始和停止。
setInterval
clearInterval
间隔 id 在组件外部声明。
间隔回调函数会增加计数器并将called消息附加到 UI。
called
当我单击“开始”时,我希望计数器每秒增加一次,并且called在按钮下方附加相应的消息。
实际情况是,单击“开始”时,计数器仅增加一次,called消息也仅增加一次。
如果我再次单击“开始”,计数器将递增,然后重置回其先前的值。
如果我继续单击“开始”,计数器将不断递增并重置回其先前的值。
有人能解释这种行为吗?
您在间隔的回调中对值进行了闭包。因此,在使用值进行第一次状态更新后,您将拥有相同的值调用,不会触发另一个渲染。count``setState(0+1)``count``setState(0+1)
count``setState(0+1)``count``setState(0+1)
使用不使用闭包的先前状态值的功能更新:
setCount((count) => count + 1);
原因相同messages:
messages
setMessages(prev => [...prev,"called"]); const start = () => { // should be a ref intervalId.current = setInterval(() => { setMessages((prev) => [...prev, "called"]); setCount((count) => count + 1); }, 1000); };
注意 使用外部范围变量而不是的另一个可能的错误useRef,有关此错误,请阅读有关useRefvs 变量差异的信息。
useRef
作为参考,这里有一个简单的计数器切换示例:
function Component() { // use ref for consisent across multiple components // see https://stackoverflow.com/questions/57444154/why-need-useref-to-contain-mutable-variable-but-not-define-variable-outside-the/57444430#57444430 const intervalRef = useRef(); const [counter, setCounter] = useState(0); // simple toggle with reducer const [isCounterOn, toggleCounter] = useReducer((p) => !p, false); // handle toggle useEffect(() => { if (isCounterOn) { intervalRef.current = setInterval(() => { setCounter((prev) => prev + 1); }, 1000); } else { clearInterval(intervalRef.current); } }, [isCounterOn]); // handle unmount useEffect(() => { // move ref value into callback scope // to not lose its value upon unmounting const intervalId = intervalRef.current; return () => { // using clearInterval(intervalRef.current) may lead to error/warning clearInterval(intervalId); }; }, []); return ( <> {counter} <button onClick={toggleCounter}>Toggle</button> </> ); }