【问题标题】:useState results in shallow copy of initial valueuseState 导致初始值的浅拷贝
【发布时间】:2021-08-28 15:46:13
【问题描述】:

我想将数据(保存为状态)传递给绘制数据图表的反应组件。该图还应该能够过滤数据。

数据是一个嵌套对象,结构如下。

{
  "mylog": {
    "entries": [
      {"Bool1": false, "Bool2": true, ...},
      {"Bool1": true, "Bool2": true, ...},
      ...
    ]
  },
  "another_log": {...},
  ...
}

我的方法是在图形组件中定义一个名为filteredData 的状态,将其设置为传递给图形的数据,然后在我要过滤数据时更新filteredData 状态。

function App(props) {
  const [data, setData] = useState({...}); // Initial data here

  return (
    <div>
      <Graph data={data} />
    </div>
  );
}

function Graph(props) {
  const [filteredData, setFilteredData] = useState(props.data);

  const filter = () => {
    setFilteredData(data => {
      ...
    });
  }

  return (
    ...
  );
}

但是,当filteredData 被过滤时,App 组件中的data 也会被过滤(这会破坏事情)。我试过在几个地方用{..props.data} 代替props.data,但这并没有解决问题。有任何想法吗?提前致谢。

这是一个最小的、可重复的示例:https://codesandbox.io/s/elastic-morse-lwt9m?file=/src/App.js

【问题讨论】:

    标签: reactjs deep-copy react-component react-state shallow-copy


    【解决方案1】:

    像整数或字符串这样的基元通过它们的值传递,而像数组这样的对象数据类型通过它们的引用传递。 在您的示例中 - data 是通过引用传递的。这使得它是可变的。

    在 React 中 - 道具应该是不可变的和自上而下的。这意味着父母可以将它喜欢的任何道具值发送给孩子,但孩子不能修改自己的道具。来自 ReactJS documentation

    无论你将组件声明为函数还是类,它都必须 永远不要修改自己的道具。

    一个解决方案是传递原始data对象的副本。

    &lt;Graph data={JSON.parse(JSON.stringify(data))} /&gt;

    更新Codepen。您仍在变异道具 - 这不是一个好主意,但它有效。

    编辑: 不推荐使用JSON.stringify,因为它在日期和非原始数据类型方面存在问题。在 JS 中进行深度克隆的其他方法 - How to Deep clone in javascript

    【讨论】:

    • 只有在 所有其他 复制/突变避免方法都用尽时,才应使用JSON 序列化/反序列化数据。这应该永远在生产代码中完成。它的使用是一种巨大的代码气味。
    • 是的,我很清楚。问题不在于如何正确地深度克隆对象,而是与可变性有关。我已经更新了答案。我什至不建议使用 setFilteredData 作为状态函数,但这是另一个讨论。
    • 我没有投反对票,顺便说一句,因为您确实从技术上回答了这个问题;我只是想指出您的“单一解决方案”不是一个好的解决方案,它只是掩盖了突变。
    【解决方案2】:

    更新本地状态正在改变道具的事实实际上告诉我们你也在改变状态。

    您过滤器中的data[log].entries = 是违规者。

    const filter = () => {
      setFilteredData((data) => {
        for (const log in data) {
          data[log].entries = data[log].entries.filter((s) => s.Bool1);
    //    ^^^^^^^^^^^^^^^^^^^ Here is the mutation
        }
        return { ...data }; // Copying data ensures React realizes
        // the state has been updated (at least in this component).
      });
    };
    

    return { ...data } 部分也是状态未正确更新的信号。这是一种在本地“修复”状态突变的解决方法。

    您应该在修改每个嵌套数组或对象之前对其进行复制。

    这是一个更正状态更新的选项,它也将解决道具问题。

    setFilteredData((data) => {
      const newData = {...data};
    
      for (const log in data) {
        newData[log] = { 
          ...newData[log],
          entries: data[log].entries.filter((s) => s.Bool1)
        }
      }
    
      return newData;
    });
    

    下面的运行示例:

    const {useState} = React;
    
    function App() {
      const [data, setData] = useState({
        mylog: {
          entries: [{ Bool1: false }, { Bool1: true }]
        }
      });
    
      return (
        <div>
          <h3>Parent</h3>
          {JSON.stringify(data)}
    
          <Graph data={data} />
        </div>
      );
    }
    
    function Graph(props) {
      const [filteredData, setFilteredData] = useState(props.data);
    
      const filter = () => {
        setFilteredData((data) => {
    
          const newData = {...data};
    
          for (const log in data) {
            newData[log] = { 
              ...newData[log],
              entries: data[log].entries.filter((s) => s.Bool1)
            }
          }
          return newData;
        });
      };
    
      return (
        <div>
          <h3>Child</h3>
          <button onClick={filter}>Filter</button>
    
          {JSON.stringify(filteredData)}
        </div>
      );
    }
    
    ReactDOM.render(<App />, document.getElementById('root'));
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
    <div id="root"></div>

    【讨论】:

      猜你喜欢
      • 2012-04-12
      • 2015-01-13
      • 2011-09-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-15
      • 1970-01-01
      相关资源
      最近更新 更多