React Hook 的闭包陷阱
参考文章
1. 从react hooks“闭包陷阱”切入,浅谈react hooks
Hooks
Hooks 是 react 自 16.8 引入的新特性,使得开发者在摆脱 class 定义组件的同时,也能够进行状态管理。这样,react 组件完全进入函数式(FP)编程范式。既然进入了函数式编程的范畴,那么闭包(closure)就成了不可回避的话题,所以,react hooks 陷阱,说白了其实就是闭包特性。
Hooks 陷阱
从实例开始:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | function Counter() { const [count, setCount] = useState(0); const handleClick = () => { setTimeout(() => { // 这个 setTimeout 会引起闭包陷阱 setCount(count + 1); }, 1000); }; const handleReset = () => { setCount(0); }; return ( <div> <p>Count: {count}</p> <button onClick={handleClick}>Increment</button> <button onClick={handleReset}>Reset</button> </div> ); } |
在上面的代码中, handleClick 的调用中有一个异步的执行,这就开启了一个宏任务,所以,这就是闭包陷阱产生的地方。相似的场景:
1 2 3 4 5 | for ( var i=0; i<5; i++) { setTimeout(()=>{ console.log(i) }, 0) } |
这是一段很经典的面试代码,如果按照上述的执行,其结果最终会打印 5 个 5 出来,而不是 0~4,其原因一样,异步任务产生的宏任务落后于主任务栈,那么该如何修改才能达到我们想要的效果?这里有两种思路:
1 2 3 4 | // 一、将 var 改成 let,保证执行上下文的正确性 for ( let i=0; i<5; i++) { ... } |
1 2 3 4 5 6 7 8 | // 二、利用闭包特性 for ( var i=0; i<5; i++ ) { ( function (i){ setTimeout(()=>{ console.log(i) }, 0) })(i); } |
而我们的 Hooks 闭包陷阱就是利用第二种方式解决的。
解决方案
先对比下有问题的写法和解决方案的写法:
1 2 3 4 5 6 | // 问题写法 const handleClick = () => { setTimeout(() => { // 这个 setTimeout 会引起闭包陷阱 setCount(count + 1); }, 1000); }; |
1 2 3 4 | // 正确写法 setTimeout(() => { setCount(currentCount => currentCount + 1); }, 1000); |
两者不同的地方是:错误写法中最终 setCount 的参数是一个具体的数值,而正确写法中传递的则是一个函数(这里基本上可以看出 react 对 FP 的编程范式的偏爱),以确保组件拿到的是最新的值。当然,这和 react 的系统有密切的关联,详情可以翻阅 react 的源码。
以上是对于 useState 这个 Hook 所产生的问题的 fix,那么对于 useEffect 也可能产生同样的问题,不过其解决方案更简单,只需要按照 react 的文档,注意补完 useEffect 的第二个参数即可
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· 单线程的Redis速度为什么快?
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码