反应 setInterval 行为
Posted
技术标签:
【中文标题】反应 setInterval 行为【英文标题】:React setInterval Behavior 【发布时间】:2021-04-05 21:53:39 【问题描述】: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
代码有两个按钮 - 开始和停止。
Start 调用 setInterval
并保存间隔 id。计时器设置为 1 秒(1000 毫秒)。
Stop 在间隔 id 上调用 clearInterval
。
区间id在组件外声明。
间隔回调函数增加一个计数器并将called
消息附加到 UI。
当我单击“开始”时,我希望计数器每秒递增一次,并在按钮下方附加一条相应的 called
消息。
实际发生的情况是,在单击“开始”时,计数器只增加一次,called
消息也是如此。
如果我再次单击“开始”,计数器会增加并随后重置为之前的值。
如果我继续点击“开始”,计数器会继续递增并重置为之前的值。
谁能解释这种行为?
【问题讨论】:
【参考方案1】:您在间隔回调中的count
值上有closure。
因此,在使用值 setState(0+1)
进行第一次状态更新后,您将拥有相同的 count
值调用 setState(0+1)
,不会触发另一个渲染。
使用functional updates,它使用之前没有闭包的状态值:
setCount((count) => count + 1);
messages
的相同原因:
setMessages(prev => [...prev,"called"]);
const start = () =>
// should be a ref
intervalId.current = setInterval(() =>
setMessages((prev) => [...prev, "called"]);
setCount((count) => count + 1);
, 1000);
;
另一个可能的错误通知
使用外部范围变量而不是 useRef
,为此请阅读 useRef
vs variable differences。
作为参考,这里有一个简单的计数器切换示例:
function Component()
// use ref for consisent across multiple components
// see https://***.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>
</>
);
【讨论】:
啊,比我快 7 秒。我建议推动使用 react ref 并使用useEffect
回调返回函数来清除卸载时的间隔,因此不会出现“无法更新卸载的 XXX”错误。
同意,应该将setInterval()
包裹在useEffect()
中。通过isCounting
等状态触发效果,点击开始和停止按钮时设置isCounting
。以上是关于反应 setInterval 行为的主要内容,如果未能解决你的问题,请参考以下文章
AS2.0 setInterval 和 clearInterval的用法