React中useEffect更新数组后另一useEffect始终取初始值如何解决
问题根源
- 闭包捕获旧值:第二个useEffect依赖为空数组,仅在组件首次渲染时执行一次,内部定时器捕获的是首次渲染时的
workingLines初始值,后续状态更新不会同步到闭包中。 - 清理函数位置错误:两个useEffect的定时器清理函数都写在了
setInterval的回调内部,根本不会触发执行,导致旧定时器无法销毁,加依赖后会重复创建新定时器,出现重复请求。 - activeLineId存储方式错误:用普通var声明的
activeLineId每次组件渲染都会被重置为0,且定时器闭包捕获的也永远是初始值,无法正确累计索引。
解决方案
我们用useRef存储workingLines的最新值规避闭包问题,同时修正定时器逻辑和索引存储方式,不需要把workingLines加入依赖即可拿到最新值,也不会出现重复请求:
修正后完整代码
import { useState, useEffect, useRef } from 'react' import axios from 'axios' let lineIds = ["3", "1", "9"] TvApp.getInitialProps = async (ctx) => { try { const res = await axios.get(`http://192.168.1.10:8090/api/tv_app_checkline/:${lineIds}`) console.log('workingLinesResponse: data: ', res.data) return { workingLines: res.data } } catch (error) { console.log('error getInitialProps:: ', error) return { workingLines: [] } // 统一返回格式,避免后续读length报错 } } export default function TvApp(props) { const [workingLines, setWorkingLines] = useState(props.workingLines || []); // 用ref存储workingLines最新值,绕开闭包限制 const workingLinesRef = useRef(props.workingLines || []); // 把activeLineId也存入ref,保证定时器从始至终不重启 const activeLineIdRef = useRef(0); // 同步workingLines最新值到ref useEffect(() => { workingLinesRef.current = workingLines; }, [workingLines]) // 第一个useEffect:15秒更新产线运行状态 useEffect(() => { const timer = setInterval(() => { const API = `http://192.168.1.10:8090/api/tv_app_checkline/:${lineIds}` axios.get(API) .then(response => { console.log('workingLines updated:: ', response.data) setWorkingLines(response.data || []) }) .catch(error => { console.log(error.message); }); }, 15000); // 清理函数要写在useEffect回调的最外层,和setInterval平级 return () => clearInterval(timer); },[]) // 第二个useEffect:10秒轮询当前产线生产数据 useEffect(() => { const timer = setInterval(() => { // 从ref中取最新的workingLines const currentWorkingLines = workingLinesRef.current; // 加边界判断,避免空数组报错 if (!currentWorkingLines.length) return; axios.get(`http://192.168.1.10:8090/api/tv_app/:${currentWorkingLines[activeLineIdRef.current]}`) .then(response => { // 处理返回的生产数据 }) .catch(error => { console.log(error.message); }) // 更新索引 activeLineIdRef.current = activeLineIdRef.current === currentWorkingLines.length - 1 ? 0 : activeLineIdRef.current + 1 }, 10000); // 外层清理定时器 return () => clearInterval(timer); }, []); // 依赖为空,定时器仅在组件首次渲染时创建一次 }
说明
useRef创建的引用对象在组件全生命周期内不变,current属性随时可以写入最新值,定时器内读workingLinesRef.current永远能拿到最新的产线列表,不需要加入useEffect依赖- 修正了清理函数的位置,组件卸载时会自动销毁旧定时器,不会出现重复请求
- 把
activeLineId也存入ref,完全规避闭包问题,第二个定时器从始至终只会创建一次,不会被状态更新触发重启
内容的提问来源于stack exchange,提问作者ilvthsgm
相关产品推荐
相关产品推荐

