You need to enable JavaScript to run this app. React中useEffect更新数组后另一useEffect始终取初始值如何解决
优惠活动
大模型
产品
解决方案
定价
更多

React中useEffect更新数组后另一useEffect始终取初始值如何解决

问题根源

  1. 闭包捕获旧值:第二个useEffect依赖为空数组,仅在组件首次渲染时执行一次,内部定时器捕获的是首次渲染时的workingLines初始值,后续状态更新不会同步到闭包中。
  2. 清理函数位置错误:两个useEffect的定时器清理函数都写在了setInterval的回调内部,根本不会触发执行,导致旧定时器无法销毁,加依赖后会重复创建新定时器,出现重复请求。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.12 22:30:04