使用 setInterval 定期附加新数据时,Recharts 图表不会更新

Recharts chart not updating when using setInterval to periodically append new data

我正在尝试设置一个网页,每 2 分钟将一个分数添加到折线图上,但它似乎不起作用。

我使用的图表是Recharts的折线图,scoreData的初始值为空列表,{time}的初始形式为{h:0, m:0, s:0}.

我正在使用上下文和挂钩,这样即使用户出于某种原因临时访问我将创建的其他页面,页面也不会停止计数。

我写下的代码如下;

const Timer = () => {
  const {
    scoreData,
    setScoreData,
    time,
    setTime,
    interv,
    setInterv,
  } = useContext(BaseContext);

const addNewData = () => {
    setScoreData([...score, {Some New data}])
}

const start = () => {
    run();
    setInterv(setInterval(run, 1000));
 };

var updatedS = time.s,
    updatedM = time.m,
    updatedH = time.h;

  const run = () => {
    updatedS++;
    if (updatedS === 60) {
      updatedM++;
      updatedS = 0;
      if (updatedM % 2 == 0) {
        addNewData();
        console.log(scoreData.length);
      }
    }
    if (updatedM === 60) {
      updatedH++;
      updatedM = 0;
    }
    return setTime({
      s: updatedS,
      m: updatedM,
      h: updatedH,
    });
  };

return (
<div>
// The code of the line chart goes in here.

<button onClick={start}>Start</button>

<button onClick={addNewData}>Test Button</button>

</div>
)

根据上面的代码,每两分钟应该向列表添加一个新数据,但是图表没有显示任何变化。

当我使用 console.log(scoreData.length) 确认 scoreData 的状态时,控制台只显示 0,这意味着数据没有被附加到列表中。

所以我尝试了一种不同的方法,制作了一个手动添加新数据的测试按钮,并且效果很好;每次我推送列表时,列表都会充满新数据。

在这种情况下,如何让图表按照我设置的时间定时接收数据?

我假设 setScoreData([...score, {Some New data}]) 应该是 setScoreData([...scoreData, {Some New data}])

你的 setInterval 收到一个固定 scoreData 的闭包并继续调用它。所以 setScoreData([...scoreData, {Some New data}]) 总是设置相同的数据。 在你的情况下,最好用 useRef hook:

来解决这个问题
  const {
    scoreData,
    setScoreData,
    time,
    setTime,
    interv,
    setInterv,
  } = useContext(BaseContext);
  const scoreDataRef = useRef(scoreData);
  scoreDataRef.current = scoreData;

  const addNewData = () => {
    setScoreData([...scoreDataRef.current, {Some New data}])
  }

测试按钮可以工作,因为它在每次渲染时都有一个新的闭包。

再见,不幸的是,对于 React Hooks,您不能以您使用它的方式使用 setInterval。问题与钩子本身有关。根据您的代码示例,您希望每秒向 scoreData 添加一个新数据。因此,您调用 useInterval 启动 runif (updatedM % 2 == 0) 调用 addNewData,最终为您的 scoreData.

增加价值

不幸的是,使用挂钩您不确定 scoreData 是否已使用您在之前 setInterval 中添加的数据更新。为什么?因为钩子是异步的!

如何解决这个问题?使用另一个挂钩,特别是自定义挂钩!

Here 一个工作示例。 让我们看看我的自定义钩子 useInterval:

function useInterval(callback, delay) {
    const savedCallback = useRef();

    // Remember the latest callback.
    useEffect(() => {
      savedCallback.current = callback;
    }, [callback]);

    // Set up the interval.
    useEffect(() => {
      function tick() {
        savedCallback.current();
      }
      if (delay !== null) {
        let id = setInterval(tick, delay);
        return () => clearInterval(id);
      }
    }, [delay]);
  }

正如您在我的自定义挂钩中看到的那样,我使用 2 useEffect: 1 来记住最新的 useInterval 回调(使用 useRef);还有一个是用来设置延迟的setInterval(这个最后一个useEffect returns一个clearInterval取消最后一个setInterval)。

然后我可以在我的组件中调用 useInterval

useInterval(() => {
if (sampling) {
  let result = _.cloneDeep(list1);
  result.push({
    x: Math.floor(Math.random() * 100),
    y: Math.floor(Math.random() * 100)
  });
  console.log(result);
  setlist1(result);
}
}, 1000);

list1 是我的值数组(你的 scoreData),sampling 是一个 bool 状态变量,我用它来 run/stop 将新值插入 list1.

最后,我的 return:

return (
    <div>
      <ScatterChart
        width={600}
        height={400}
        margin={{ top: 20, right: 20, bottom: 20, left: 20 }}
      >
        <CartesianGrid />
        <XAxis type="number" dataKey={"x"} />
        <YAxis type="number" dataKey={"y"} />
        <Tooltip cursor={{ strokeDasharray: "3 3" }} />
        <Legend />
        <Scatter
          name="values"
          data={list1}
          fill="#8884d8"
          line
          shape="circle"
        />
      </ScatterChart>
      <button onClick={start}>
        {sampling ? "Stop sampling" : "Start sampling"}
      </button>
    </div>
  );

我使用了 ScatterChart(图形上等于 LineChart)和一个按钮来 start/stop 数据采样。