这是主包在面试中遇到的一道题目,面试官的问题是:"这个页面初次展示出来时Count和step的值是什么,我点击按钮count和step的值有什么变化?“
这个题目主包回答的不好,所以想做一个总结。
题目
import React, { useState, useEffect } from 'react';
function useInterval(callback, delay) {
useEffect(() => {
const id = setInterval(() => {
console.log('[Timer Tick]的 count:', callback._countSnapshot);
callback();
}, delay);
return () => clearInterval(id);
}, [delay]);
}
export default function BuggyCounter() {
const [count, setCount] = useState(0);
const [step, setStep] = useState(1);
function tick() {
setCount(count + step);
}
// 注册定时器
useInterval(tick, 1000);
return (
<div>
<h1>Count: {count}</h1>
<div>
<button onClick={() => setStep((s) => s + 1)}>Increase Step</button>
<span> 当前 step: {step} </span>
</div>
</div>
);
}
分析
首先,先好好阅读代码,主包现在发现主包在面试中有一个致命的问题就是,很多问题还没明白面试官的意思,或者像代码,还没明白代码的意思就已经开始胡说了。往往是越说越远,其实结束面试之后主包再次阅读这段答的稀烂的代码的时候,发现很能看懂。所以不要着急,先明白面试官 的意思在作答。
好的,我们来解析代码,这个函数什么作用呢。首先我们要明白这是自定义了一个Hook。传入两个参数:callback是一个函数,delay动态参数用于设置定时器的。useEffect的部分就是以delay作为依赖,并在return这个处理副作用的部分进行一个清空计时器。它实现的功能是:安全的实行定时器,在组件卸载或 delay
变化时,自动清除之前的定时器。
接下来看tick函数,就是去改变count的状态为count+step;
调用定时器,每1000ms执行一次。
好的,现在解析完毕,开始作答。在刚进入页面的时候,我们应该看的到一瞬间的count:0,step:1;但是1000ms后count变为1了。之后如果我们不点击按钮就会保持这样一直不变。
问题
当我们点击按钮之后,只有step的值会增加,而count的值不会增加。
现在我们来解释为什么会这样:
首先,进入页面之后,会因为初态的问题,count和step会保持初态0和1;但是1000ms后,定时器起效了此时tick函数起作用,但是它捕获的是初态的count和step,在定时器循环的过程中,由于闭包问题,count和step一直没有得到更新,所以count不会改变。
控制台呢
你会发现callback函数中根本就没有 _countSnapshot这个属性,所以他是迷惑我们的,一直是undefined。
怎么更改
方案1
setCount((count)=> count + step);
这样就好,我们在每一次更新时,通过回调获取最新的count值。
方案2:将 tick
加入 useInterval
的依赖
修改 useInterval
,使其响应 callback
的变化:
function useInterval(callback, delay) {
useEffect(() => {
const id = setInterval(callback, delay); // 直接使用最新的 callback
return () => clearInterval(id);
}, [delay, callback]); // 依赖 callback
}
方案3:useRef
function useInterval(callback, delay) {
const savedCallback = useRef();
// 保存最新的回调
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// 设置定时器
useEffect(() => {
function tick() {
savedCallback.current(); // 调用最新的回调
}
if (delay !== null) {
const id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
“人间自有真情在 ,宜将寸心报春晖。”