Lodash delay方法在React函数组件内无法读取最新state的原因是什么
问题原因与解决方案
核心原因:函数组件的闭包捕获特性
你遇到的是React函数组件非常典型的闭包陷阱问题,和lodash的delay本身无关——delay本质就是对原生setTimeout的封装,你换成原生setTimeout也会出现相同现象:
- 你写的
useEffect依赖数组为空,所以只会在组件首次挂载时执行1次 - 首次执行时,
testFunction是在首次渲染的作用域中创建的,闭包捕获到的progress值固定为初始值0 - 后续你通过
delay调度的3次调用,用的都是首次渲染生成的旧版testFunction,所以永远只能读到初始的0。即便后续progress更新触发组件重渲染,生成了捕获到新state的新版testFunction,也不会被用到,因为useEffect没有重新执行。
类组件正常工作的原因
类组件的state是挂载在组件实例this上的:
- 你调用
testFunction时读取的是this.state.progress,每次都会访问当前实例上最新的state值 - 不存在闭包捕获固定值的问题,所以不管什么时候调度调用,都能读到更新后的state。
可行的解决方案
最常用的方案是用useRef存储最新的state值,useRef的current属性是可变的,不会被闭包捕获固定值:
import React, { useEffect, useState, useRef } from 'react'; import { delay } from 'lodash'; const App = () => { const [progress, setProgress] = useState(0); // 用ref存储最新的progress值 const progressRef = useRef(progress); console.log(progress); const testFunction = () => { // 读取ref的current属性拿到最新值 console.log('inside function', progressRef.current); }; useEffect(() => { const seconds = 40; const attempts = 3; const milliseconds = 4000; const timer = setInterval(() => { setProgress((prevProgress) => { const newProgress = prevProgress >= 100 ? 100 : prevProgress + 1; // 更新state的同时同步更新ref的值 progressRef.current = newProgress; return newProgress; }); }, seconds * attempts * 10); for (let i = 0; i < attempts; i++) { delay(testFunction, milliseconds * (i + 1)); } // 组件卸载时清除定时器,避免内存泄漏 return () => clearInterval(timer); }, []); return <div>Hello World</div>; }; export default App;
其他可选方案:
- 把
progress加入useEffect的依赖数组,每次progress更新都重新生成testFunction并调度,需要注意做好去重逻辑,避免重复调度多次 - 用
useCallback包装testFunction,再用useRef存储最新的testFunction实例,调度时读取ref中的最新函数
内容的提问来源于stack exchange,提问作者Sonny49
相关产品推荐
相关产品推荐

