如果传递变量而不是显式文本,React setState 钩子不会更新依赖元素

React setState hook not updating dependent element if passed a variable as opposed to explicit text

我正处于折腾 React 和只使用 vanilla JS 的边缘,但我想我会先在这里检查一下。我只是想将包含对象的变量的内容传递到状态中,并让其更新依赖于它的元素。如果我向 setState 传递一个包含该对象的变量,它就不起作用。如果我将它传递的对象的显式文本传递给它。

使用 React v 18.0.0

function buttonHandler(e) {
    e.preventDefault()
    let tmpObject = {...chartData}
    tmpObject.datasets[0].data = quoteData.map(entry => entry['3'])
    tmpObject.datasets[1].data = quoteData.map(({fastEma}) => fastEma)
    tmpObject.datasets[2].data = quoteData.map(({slowEma}) => slowEma)
    tmpObject.labels = quoteData.map(entry => new Date(entry.timestamp).toLocaleTimeString())
    console.log("from button:", tmpObject)
    setChartData(prevState => {
        console.log("tmpObject",tmpObject)
        return tmpObject
    })

return <div>
    <button onClick={buttonHandler}>Update</button>

    <Line options={chartOptions} data={chartData}/>
</div>

当我 运行 上面的内容时,console.log 的输出与它应该的完全一样,但元素不会更新。如果我从控制台复制对象输出并将其显式粘贴到代码中,它确实有效。

function buttonHandler(e) {
    e.preventDefault()
    setChartData({...}) 

我已经尝试了以下语句的所有可以想象的变体,但都无济于事...

return {...prevState, ...tmpObject}

如果有任何建议,我将不胜感激。

编辑: 作为另一个测试,我添加了以下 HTML 元素以查看它是否已更新。它得到更新并显示预期数据。尽管如此,我还是很难理解为什么图表会在我传递显式文本时更新,但如果我传递一个变量则不会更新。

<p>{`${new Date().toLocaleTimeString()} {JSON.stringify(chartData)}`</p>

问题是状态突变。即使您浅复制了 chartData 状态,您也应该记住这是引用复制。每个 属性 仍然是对原始 chartData 对象的引用。

function buttonHandler(e) {
  e.preventDefault();

  let tmpObject = { ...chartData }; // <-- shallow copy ok
  tmpObject.datasets[0].data = quoteData.map(entry => entry['3']); // <-- mutation!!
  tmpObject.datasets[1].data = quoteData.map(({ fastEma }) => fastEma); // <-- mutation!!
  tmpObject.datasets[2].data = quoteData.map(({ slowEma }) => slowEma); // <-- mutation!!
  tmpObject.labels = quoteData.map(
    entry => new Date(entry.timestamp).toLocaleTimeString()
  );

  console.log("from button:", tmpObject);

  setChartData(prevState => {
    console.log("tmpObject",tmpObject);
    return tmpObject;
  });
}

在 React 中,不仅下一个状态需要是一个新的对象引用,任何正在更新的嵌套状态也是如此。

参见 Immutable Update Pattern - 这是一个 Redux 文档,但真正解释了为什么使用可变更新是 React 的关键。

function buttonHandler(e) {
  e.preventDefault();

  setChartData(chartData => {
    const newChartData = {
      ...chartData,  // <-- shallow copy previous state
      labels: quoteData.map(
        entry => new Date(entry.timestamp).toLocaleTimeString()
      ),
      datasets: chartData.datasets.slice(),  // <-- new datasets array
    };

    newChartData.datasets[0] = {
      ...newChartData.datasets[0],                   // <-- shallow copy
      data: quoteData.map(entry => entry['3']),      // <-- then update
    };
    newChartData.datasets[1] = {
      ...newChartData.datasets[1],                   // <-- shallow copy
      data: quoteData.map(({ fastEma }) => fastEma), // <-- then update
    };
    newChartData.datasets[2] = {
      newChartData.datasets[2],                      // <-- shallow copy
      data: quoteData.map(({ slowEma }) => slowEma), // <-- then update
    };

    return newChartData;
  });
}

使用依赖于 chartData 状态的 useEffect 挂钩检查您的工作:

useEffect(() => {
  console.log({ chartData });
}, [chartData]);

如果仍然存在更新问题,请检查 Line 组件的代码,看看它是否正在对传递的 data prop 进行任何类型的安装记忆。