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;
核心问题点
定时器清理逻辑错误
你把clearInterval放在了setInterval的回调函数内部,这意味着每次定时器触发时都会返回一个清理函数,但这个函数根本不会被React调用。正确的做法是将清理函数放在useEffect的返回值中,确保组件卸载或依赖项变化时清理之前的定时器。依赖项导致重复创建定时器
useEffect的依赖是整个props对象,当props中任何属性变化时,都会重新执行useEffect,创建新的定时器,而旧的定时器没有被清理,导致多个定时器同时运行,每次触发都会向数组添加值。Labels的更新逻辑混乱
- 使用
labels.current.value是错误的,useRef的current属性直接赋值即可,不需要.value。 - 用
dataSet.slice来处理labels的截断,两者状态不同步,容易导致长度不一致。 - labels用useRef存储,但图表依赖labels更新时,useRef的变化不会触发组件重渲染,可能导致图表不更新。
- 使用
数组长度判断逻辑错误
判断条件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;
修复说明
- 定时器管理:用
useRef存储定时器ID,在useEffect执行前先清理旧的定时器,确保每次只有一个定时器运行。 - Labels改为State:将labels从useRef改为useState,确保labels更新时触发组件重渲染,图表能实时更新。
- 依赖项优化:仅依赖
props.Parameters.temperature,避免因props中其他无关属性变化导致重复创建定时器。 - 同步更新逻辑:labels和dataSet都使用函数式更新,基于前一次状态进行修改,确保两者长度同步,且最大长度保持为10。
- 修正长度判断:使用
length >=10,当数组达到10个元素时开始截断,确保始终不超过10个数据点。
内容的提问来源于stack exchange,提问作者Kaaraj Wadhwa
相关产品推荐
相关产品推荐

