将一个大数组包装在一个对象中以避免在 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 更新您的状态 - 尽量避免 forceUpdatethis.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)
  }
})