useState 导致初始值的浅拷贝

useState results in shallow copy of initial value

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

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

{
  "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

原始类型,例如整数或字符串,通过它们的值传递,而 Object data-types,例如数组,通过它们的引用传递。 在您的示例中 - data 是通过引用传递的。这使得它可变。

在 React 中 - props 应该是不可变的并且 top-down。这意味着 parent 可以将它喜欢的任何道具值发送给 child,但是 child 不能修改自己的道具。来自 ReactJS documentation

Whether you declare a component as a function or a class, it must never modify its own props.

一个解决方案是传递一份您的原件data object.

<Graph data={JSON.parse(JSON.stringify(data))} />

已更新 Codepen。你还在改变 props - 这不是个好主意,但它确实有效。

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

更新本地状态会改变 prop 这一事实实际上告诉我们你也在改变状态。

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>