blog
blog copied to clipboard
React Hooks 闭包陷阱
实例
export default function App() {
const [count, setCount] = useState(0);
useEffect(() => {
setInterval(() => {
console.log(count);
}, 1000);
}, []);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
依赖项 deps 的判断条件:
- undefined 或 null 每次都执行
- 空数组 [] 只执行一次
- 其他执行比较函数
原因
在这种情况下,点击 button 只会影响 button 按钮的文案数字变化, 定时任务里面的 count 一直是 0。
这种 useEffect 的函数里引用了某个 state,形成了闭包情况被称为闭包陷阱。
原因是 useEffect 的依赖项为空时, setCount 执行后的刷新不会影响 useEffect 中的定时函数中保存的闭包,它继续默默执行了。
调用了setCount 修改了 count,页面触发 render,组件内部代码会重新执行一遍,重新声明了一个 count,count 就不再是原来那个 count,这里点击事件里作用域中的 count 还是旧的 count,这是两个不同的 count。
count 存在了对应 hook 的 memoizedState 属性里,不会更新
hook 的结构,以链表形式互相关联
type Hook = {
memoizedState: any,
baseState: any,
baseUpdate: Update<any, any> | null,
queue: UpdateQueue<any, any> | null,
next: Hook | null,
};
解决方法
- 更新依赖项
- 使用 useRef
1. 更新依赖项
将 count 作为 useEffect 的依赖项,从而重新定义定时函数; 但是这种情况下老的定时函数并没有被清除,因此需要改造代码
export default function App() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
console.log(count);
}, 1000);
return () => {
clearInterval(timer);
};
}, [count]);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
这种情况下,清除老的定时任务,建立新的定时任务。就可以改变定时任务中 count 的值了。
2. 使用 useRef
闭包陷阱产生的原因就是 useEffect 的函数里引用了某个 state,形成了闭包,绕过这个机制,直接取使用引用取对应的值来避免这个问题,useRef 就可以达到这个效果:
export default function App() {
const [count, setCount] = useState(0);
const ref = useRef(count);
useEffect(() => {
setInterval(() => {
console.log(ref.current);
}, 1000);
}, []);
return (
<button
onClick={() => {
setCount(count + 1);
ref.current = count + 1;
}}
>
{count}
</button>
);
}