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>
我想将数据(保存为状态)传递给绘制该数据的反应组件。该图表还应该能够过滤数据。
数据是一个嵌套对象,结构如下。
{
"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>