You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React项目中setInterval行为异常,导致State数组被多次修改

问题排查:React中setInterval多次添加值到数组的问题

你的React组件中使用setInterval更新数组并通过ChartJS展示折线图,但每次会向数组中添加多个值而非单个,问题根源在于定时器的创建与清理逻辑、依赖项配置以及状态更新的一致性上,以下是具体分析和修复方案:

原始代码

import styles from "./LineGraph.module.css";
import { useEffect, useState, useRef } from "react";
import { Line } from "react-chartjs-2";
import {
  Chart as ChartJS,
  LineElement,
  CategoryScale,
  LinearScale,
  PointElement,
} from "chart.js";

ChartJS.register(LineElement, CategoryScale, LinearScale, PointElement);

const Time = () => {
  let now = new Date();
  let hour = now.getHours();
  let minute = now.getMinutes();
  let second = now.getSeconds();

  return `${hour}-${minute}-${second}`;
};

const LineGraph = (props) => {
  const [dataSet, setDataset] = useState([0]);
  const labels = useRef([0]);
  const temperature=props.temperatureValue;

  useEffect(() => {
    console.log(props);
    const temperature= props.Parameters.temperature;
    console.log(props.Parameters.temperature);
    const interval = setInterval(() => {

      if (labels.current.length > 10) {
        const newLabels = dataSet.slice(1, dataSet.length);
        const newLabel = Math.floor(Math.random() * 1000);
        labels.current.value = [...newLabels, newLabel];
      } else {
        labels.current = [...labels.current, Time()];
      }
      setDataset((dataSet) => {
        if (dataSet.length > 10) {
          const newDataSet = dataSet.slice(1, dataSet.length);
          const newValue = Math.floor(Math.random() * 1000);
          return [...newDataSet, newValue];
        } else {
          const newValue = Math.floor(Math.random() * 1000);
          return [...dataSet, temperature];

        }
      });
      return () => {
        clearInterval(interval);
      };
    }, 10000);
  }, [props]);

  const data = {
    labels: labels.current,
    datasets: [
      {
        data: dataSet,
        fill: true,
        backgroundColor: "rgb(92, 158, 231)",
        borderColor: "rgb(92, 158, 231)",
        pointRadius: 5,
        pointHoverRadius: 8,
        pointBackgroundColor: "rgb(92, 158, 231)",
        pointBorderColor: "rgb(92, 158, 231)",
        pointHoverBackgroundColor: "rgb(92, 158, 231)",
        pointHoverBorderColor: "rgb(92, 158, 231)",
        borderWidth: 2,
        tension: 0.5,
      },
    ],
  };
  return (
    <>
      <Line data={data}></Line>
    </>
  );
};

export default LineGraph;

核心问题点

  1. 定时器清理逻辑错误
    你把clearInterval放在了setInterval的回调函数内部,这意味着每次定时器触发时都会返回一个清理函数,但这个函数根本不会被React调用。正确的做法是将清理函数放在useEffect的返回值中,确保组件卸载或依赖项变化时清理之前的定时器。

  2. 依赖项导致重复创建定时器
    useEffect的依赖是整个props对象,当props中任何属性变化时,都会重新执行useEffect,创建新的定时器,而旧的定时器没有被清理,导致多个定时器同时运行,每次触发都会向数组添加值。

  3. Labels的更新逻辑混乱

    • 使用labels.current.value是错误的,useRef的current属性直接赋值即可,不需要.value。
    • 用dataSet.slice来处理labels的截断,两者状态不同步,容易导致长度不一致。
    • labels用useRef存储,但图表依赖labels更新时,useRef的变化不会触发组件重渲染,可能导致图表不更新。
  4. 数组长度判断逻辑错误
    判断条件length > 10会导致数组长度达到11时才截断,而你期望的是最大长度为10,应该改为length >= 10。

修复后的代码

import styles from "./LineGraph.module.css";
import { useEffect, useState, useRef } from "react";
import { Line } from "react-chartjs-2";
import {
  Chart as ChartJS,
  LineElement,
  CategoryScale,
  LinearScale,
  PointElement,
} from "chart.js";

ChartJS.register(LineElement, CategoryScale, LinearScale, PointElement);

const getTimeLabel = () => {
  const now = new Date();
  const hour = now.getHours();
  const minute = now.getMinutes();
  const second = now.getSeconds();
  return `${hour}-${minute}-${second}`;
};

const LineGraph = (props) => {
  const [dataSet, setDataset] = useState([0]);
  const [labels, setLabels] = useState([0]); // 将labels改为state,确保变化触发重渲染
  const intervalRef = useRef(null); // 用ref存储定时器ID,方便清理

  useEffect(() => {
    // 清理之前的定时器
    if (intervalRef.current) {
      clearInterval(intervalRef.current);
    }

    const temperature = props.Parameters?.temperature;
    // 创建新的定时器
    intervalRef.current = setInterval(() => {
      setLabels(prevLabels => {
        if (prevLabels.length >= 10) {
          // 保持最多10个标签,移除第一个,添加新时间标签
          return [...prevLabels.slice(1), getTimeLabel()];
        } else {
          return [...prevLabels, getTimeLabel()];
        }
      });

      setDataset(prevDataSet => {
        if (prevDataSet.length >= 10) {
          // 保持最多10个数据点,移除第一个,添加新温度值
          return [...prevDataSet.slice(1), temperature];
        } else {
          return [...prevDataSet, temperature];
        }
      });
    }, 10000);

    // 组件卸载或依赖变化时清理定时器
    return () => {
      clearInterval(intervalRef.current);
    };
  }, [props.Parameters?.temperature]); // 仅依赖temperature,避免不必要的重渲染

  const data = {
    labels: labels,
    datasets: [
      {
        data: dataSet,
        fill: true,
        backgroundColor: "rgb(92, 158, 231)",
        borderColor: "rgb(92, 158, 231)",
        pointRadius: 5,
        pointHoverRadius: 8,
        pointBackgroundColor: "rgb(92, 158, 231)",
        pointBorderColor: "rgb(92, 158, 231)",
        pointHoverBackgroundColor: "rgb(92, 158, 231)",
        pointHoverBorderColor: "rgb(92, 158, 231)",
        borderWidth: 2,
        tension: 0.5,
      },
    ],
  };

  return <Line data={data} />;
};

export default LineGraph;

修复说明

  1. 定时器管理:用useRef存储定时器ID,在useEffect执行前先清理旧的定时器,确保每次只有一个定时器运行。
  2. Labels改为State:将labels从useRef改为useState,确保labels更新时触发组件重渲染,图表能实时更新。
  3. 依赖项优化:仅依赖props.Parameters.temperature,避免因props中其他无关属性变化导致重复创建定时器。
  4. 同步更新逻辑:labels和dataSet都使用函数式更新,基于前一次状态进行修改,确保两者长度同步,且最大长度保持为10。
  5. 修正长度判断:使用length >=10,当数组达到10个元素时开始截断,确保始终不超过10个数据点。

内容的提问来源于stack exchange,提问作者Kaaraj Wadhwa

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 11:25:00