将一个大数组包装在一个对象中以避免在 React 中进行深度克隆是作弊吗?
Is it cheating to wrap a large array in an object to avoid deep cloning in React?
我很迷茫
假设我有一个结构如下的消息数组:
this.state.messages = [
{ message_id: 0, body: "hello", author: "john" },
{ message_id: 1, body: "how", author: "lilly" },
{ message_id: 2, body: "are", author: "abe" },
{ message_id: 3, body: "you", author: "josh" }
];
并且用户想要编辑第三条消息。 React 说是不可变的。也就是这样做不好:
this.state.messages[2].body = "test";
this.forceUpdate();
问题在于 React 无法判断消息状态变量已更改,因为它的引用仍然相同。首先,这有关系吗?无论如何我都在调用 forceUpdate
,所以无论如何它都会重新渲染。如果您使用 PureComponent
或自定义 shouldComponentUpdate
函数,这难道不重要吗?
好吧,让我们说它确实很重要(出于某种原因)。然后建议进行深拷贝,以便 React 知道对象已更改。但是多深才够深?
例如,做这个就够了吗?
let x = this.state.messages.slice(0);
x[2].body = "test";
this.setState({ messages: x });
这复制了对所有数组元素的引用,但它并不完全深!因为(当然在我们执行 setState 之前),this.state.messages[0].body === x[0].body
。未复制字符串。它仍然与前一个对象共享内部状态。它不是完全深拷贝。
好吧,但是如果我们不需要做一个完整的深拷贝,唯一重要的是父节点引用发生变化,那么我们就不能直接作弊吗?
this.state.messages = { a: [
{ message_id: 0, body: "hello", author: "john" },
{ message_id: 1, body: "how", author: "lilly" },
{ message_id: 2, body: "are", author: "abe" },
{ message_id: 3, body: "you", author: "josh" }
]};
let x = { a: this.state.messages.a };
this.setState({ messages: x });
现在不是复制所有消息引用(其中可能有数千个),而是单个指针更改。但这两条消息都不是深拷贝。如果切片版本没问题,那么这不应该也可以吗?深拷贝似乎也不是。
如果后一种方法没问题,是否有更优雅的编写方式,这样您就不需要任意容器对象(在本例中为 a
)?一种仅以某种方式更改指针以向 React 发出内容更改信号的方法?
编辑:好吧,我觉得我解释得不够好。对不起,让我再试一次。暂时忘掉性能。我想知道的是:您是否需要进行深度克隆以进行状态更新?例如,在上面发布的示例中,更新消息数组中正文的正确 方法是什么? .slice(0)
不是深拷贝,因为它仍然共享内部结构(它只是复制引用)。这个可以吗?或者你需要做一个深拷贝才合适吗?如果没问题,那么只拥有一个包装器对象并且只更改那个指针不应该也可以吗?
进一步编辑:我不确定我是不是没有正确解释自己,还是遗漏了一些非常明显的东西。 React 是否需要深度克隆?我觉得这是一个非此即彼的事情。我很怀疑 React 需要 70% 的深度克隆,而不是 30%。它要么需要一个完整的深度克隆,要么不需要。如果没有,那么仅仅更改单个包装器指针是否就足够了?如果不是,那么 slice(0)
和 Object.assign
也 还不够吗,因为它们也是浅克隆?在这些克隆的情况下,内部对象仍然保持相同的结构(例如,字符串引用没有改变)。
您不应该使用强制更新。
你不应该改变状态对象——它应该是不可变的是有原因的(更容易调试、更好的性能等)
https://redux.js.org/faq/immutable-data#what-are-the-benefits-of-immutability
https://medium.com/@kkranthi438/dont-mutate-state-in-react-6b25d5e06f42
https://daveceddia.com/why-not-modify-react-state-directly/
除非存在真正的性能问题,否则不要担心性能 - 像这样的过早优化实际上可能会损害性能,并减慢您的开发速度(这会使您的应用程序更难调试)。
"Thousands of objects" 对现代计算机/移动设备来说毫无意义。在打破不变性约定之前,您可以尝试许多其他事情——您可以尝试将状态分成更小的块,使用选择器,更好地组织您的组件,这样当只有一小部分状态发生变化时,不必重新渲染所有内容.
为您的 state
使用平面/简单对象通常更好 - 因此创建它们的新副本要容易得多。 (当您执行 Object.assign
或 { ...foo }
时,您只会得到浅表副本。)如果您不能消除深层嵌套对象,您可以尝试添加第 3 方库,如 immutability-helper,或 lodash.cloneDeep
简而言之,仅使用 setState
更新您的状态 - 尽量避免 forceUpdate
和 this.state.foo = bar
之类的东西。即使某些深度嵌套的对象仍在引用它们的旧状态,只要您遵守规则,在大多数情况下您应该没问题。
但是,请尽可能使状态对象变浅。
在您的示例中,您提到 this.state.messages[0].body === x[0].body
字符串 确实 已复制。字符串总是在 JS 中被复制。您的表达式正在比较两个字符串值 - 而不是它们的引用。
// Given:
let obj = { foo: { bar: 'baz' } };
let fooObj = obj.foo; // fooObj is a reference to obj.foo;
let str = obj.foo.bar; // str is a COPY of the string 'baz';
str === fooOjb.bar // true, you are comparing their values, not references.
obj.foo.bar = 'baz2';
fooObj === obj.foo; // true, because you are comparing their references.
str === obj.foo.bar // false - str value does not change when obj.foo.bar changes.
在 react
中 绝对需要 不变性吗?
简短回答:否。你想做什么,就可以做什么。当你的新状态的一部分仍然引用旧状态时,React 不会向你抛出错误。
但是,永远不要直接改变你的状态。始终通过 setState
进行。不要太担心你的状态对象是否是 100% 深度克隆的。只要您能确保您的应用程序的任何部分都不会修改您的状态,React
就可以处理其余部分。
有一个非常好的示例,可以将待办事项列表存储为待办事项及其 ID 列表的映射:
const state = {
todos: {
0: "Buy a milk",
1: "Buy a bread",
},
todosList: [0, 1]
}
也许你应该拆分你的状态并将 messages
存储为地图,并将 ID
作为键。它给您 O(1)
时间来添加、更改和删除消息。
如果您将消息映射存储为单独的状态,您将能够像这样更改任何消息:
this.setState({
[messageId]: newValue
})
停止改变你的数据!
React 的主要功能之一是对 'state' 对象中保存的数据进行变更检测。这种变化检测的关键是确保您始终使用以前的状态,修改您需要的任何数据,React 将处理变化检测,然后尽可能有效地相应地更新 UI。
this.setState((prevState)=> {
return {
...prevState,
messages: prevState.messages.map((obj,i)=> i === 2 ? { ...obj, body:"test" } : obj)
}
})
我很迷茫
假设我有一个结构如下的消息数组:
this.state.messages = [
{ message_id: 0, body: "hello", author: "john" },
{ message_id: 1, body: "how", author: "lilly" },
{ message_id: 2, body: "are", author: "abe" },
{ message_id: 3, body: "you", author: "josh" }
];
并且用户想要编辑第三条消息。 React 说是不可变的。也就是这样做不好:
this.state.messages[2].body = "test";
this.forceUpdate();
问题在于 React 无法判断消息状态变量已更改,因为它的引用仍然相同。首先,这有关系吗?无论如何我都在调用 forceUpdate
,所以无论如何它都会重新渲染。如果您使用 PureComponent
或自定义 shouldComponentUpdate
函数,这难道不重要吗?
好吧,让我们说它确实很重要(出于某种原因)。然后建议进行深拷贝,以便 React 知道对象已更改。但是多深才够深?
例如,做这个就够了吗?
let x = this.state.messages.slice(0);
x[2].body = "test";
this.setState({ messages: x });
这复制了对所有数组元素的引用,但它并不完全深!因为(当然在我们执行 setState 之前),this.state.messages[0].body === x[0].body
。未复制字符串。它仍然与前一个对象共享内部状态。它不是完全深拷贝。
好吧,但是如果我们不需要做一个完整的深拷贝,唯一重要的是父节点引用发生变化,那么我们就不能直接作弊吗?
this.state.messages = { a: [
{ message_id: 0, body: "hello", author: "john" },
{ message_id: 1, body: "how", author: "lilly" },
{ message_id: 2, body: "are", author: "abe" },
{ message_id: 3, body: "you", author: "josh" }
]};
let x = { a: this.state.messages.a };
this.setState({ messages: x });
现在不是复制所有消息引用(其中可能有数千个),而是单个指针更改。但这两条消息都不是深拷贝。如果切片版本没问题,那么这不应该也可以吗?深拷贝似乎也不是。
如果后一种方法没问题,是否有更优雅的编写方式,这样您就不需要任意容器对象(在本例中为 a
)?一种仅以某种方式更改指针以向 React 发出内容更改信号的方法?
编辑:好吧,我觉得我解释得不够好。对不起,让我再试一次。暂时忘掉性能。我想知道的是:您是否需要进行深度克隆以进行状态更新?例如,在上面发布的示例中,更新消息数组中正文的正确 方法是什么? .slice(0)
不是深拷贝,因为它仍然共享内部结构(它只是复制引用)。这个可以吗?或者你需要做一个深拷贝才合适吗?如果没问题,那么只拥有一个包装器对象并且只更改那个指针不应该也可以吗?
进一步编辑:我不确定我是不是没有正确解释自己,还是遗漏了一些非常明显的东西。 React 是否需要深度克隆?我觉得这是一个非此即彼的事情。我很怀疑 React 需要 70% 的深度克隆,而不是 30%。它要么需要一个完整的深度克隆,要么不需要。如果没有,那么仅仅更改单个包装器指针是否就足够了?如果不是,那么 slice(0)
和 Object.assign
也 还不够吗,因为它们也是浅克隆?在这些克隆的情况下,内部对象仍然保持相同的结构(例如,字符串引用没有改变)。
您不应该使用强制更新。 你不应该改变状态对象——它应该是不可变的是有原因的(更容易调试、更好的性能等)
https://redux.js.org/faq/immutable-data#what-are-the-benefits-of-immutability
https://medium.com/@kkranthi438/dont-mutate-state-in-react-6b25d5e06f42
https://daveceddia.com/why-not-modify-react-state-directly/
除非存在真正的性能问题,否则不要担心性能 - 像这样的过早优化实际上可能会损害性能,并减慢您的开发速度(这会使您的应用程序更难调试)。
"Thousands of objects" 对现代计算机/移动设备来说毫无意义。在打破不变性约定之前,您可以尝试许多其他事情——您可以尝试将状态分成更小的块,使用选择器,更好地组织您的组件,这样当只有一小部分状态发生变化时,不必重新渲染所有内容.
为您的 state
使用平面/简单对象通常更好 - 因此创建它们的新副本要容易得多。 (当您执行 Object.assign
或 { ...foo }
时,您只会得到浅表副本。)如果您不能消除深层嵌套对象,您可以尝试添加第 3 方库,如 immutability-helper,或 lodash.cloneDeep
简而言之,仅使用 setState
更新您的状态 - 尽量避免 forceUpdate
和 this.state.foo = bar
之类的东西。即使某些深度嵌套的对象仍在引用它们的旧状态,只要您遵守规则,在大多数情况下您应该没问题。
但是,请尽可能使状态对象变浅。
在您的示例中,您提到 this.state.messages[0].body === x[0].body
字符串 确实 已复制。字符串总是在 JS 中被复制。您的表达式正在比较两个字符串值 - 而不是它们的引用。
// Given:
let obj = { foo: { bar: 'baz' } };
let fooObj = obj.foo; // fooObj is a reference to obj.foo;
let str = obj.foo.bar; // str is a COPY of the string 'baz';
str === fooOjb.bar // true, you are comparing their values, not references.
obj.foo.bar = 'baz2';
fooObj === obj.foo; // true, because you are comparing their references.
str === obj.foo.bar // false - str value does not change when obj.foo.bar changes.
在 react
中 绝对需要 不变性吗?
简短回答:否。你想做什么,就可以做什么。当你的新状态的一部分仍然引用旧状态时,React 不会向你抛出错误。
但是,永远不要直接改变你的状态。始终通过 setState
进行。不要太担心你的状态对象是否是 100% 深度克隆的。只要您能确保您的应用程序的任何部分都不会修改您的状态,React
就可以处理其余部分。
有一个非常好的示例,可以将待办事项列表存储为待办事项及其 ID 列表的映射:
const state = {
todos: {
0: "Buy a milk",
1: "Buy a bread",
},
todosList: [0, 1]
}
也许你应该拆分你的状态并将 messages
存储为地图,并将 ID
作为键。它给您 O(1)
时间来添加、更改和删除消息。
如果您将消息映射存储为单独的状态,您将能够像这样更改任何消息:
this.setState({
[messageId]: newValue
})
停止改变你的数据!
React 的主要功能之一是对 'state' 对象中保存的数据进行变更检测。这种变化检测的关键是确保您始终使用以前的状态,修改您需要的任何数据,React 将处理变化检测,然后尽可能有效地相应地更新 UI。
this.setState((prevState)=> {
return {
...prevState,
messages: prevState.messages.map((obj,i)=> i === 2 ? { ...obj, body:"test" } : obj)
}
})