【问题标题】:React - Updating state (array of objects). Do I need to deep copy the array?React - 更新状态(对象数组)。我需要深度复制数组吗?
【发布时间】:2019-05-24 15:59:36
【问题描述】:

我有以下state

const[images,setImages] = useState([
  {src: 'stringSRC1', selected: false},
  {src: 'stringSRC2', selected: false},
  {src: 'stringSRC3', selected: false}
]);

我正在使用以下代码更新它(切换选定状态):

function handleImageClick(index) {
  props.setImages((prevState)=>{
    const aux = Array.from(prevState);
    aux[index].selected = !aux[index].selected;
    return aux;
  });
}

它按预期工作。但我想到了一件事。

当我从 prevState 复制数组时,我正在创建一个新数组,但对象(存储为引用)将保持不变。我已经测试过,它们确实如此像这样复制数组时不会改变。

问题

这是一种不好的做法吗?我是否应该费心深度复制数组,例如创建一个新数组并创建全新的对象?还是这样就好了?

【问题讨论】:

  • 仅供参考,answer you've currently accepted 不正确,详细信息in the documentation
  • @T.J.Crowder 嘿 T.J,感谢您的评论。为什么不正确?看看我使用来自immerproduce 制作的this example。不可变更新会更改数组引用和已修改的每个项目,但不会更改更新未触及的项目的引用。 produce 不是经常用于执行不可变的 React 状态更新吗?我不会说数组的内容无关紧要,但我想你不必深度克隆它。你同意吗?
  • The documentation 很清楚不能直接修改状态项。如果你这样做,它们可能不会重新渲染,包括(但不限于,我不认为)组件在其 props 不变的情况下避免重新渲染的情况(通过React.memoPureComponent 或 @ 987654336@).
  • 我没有使用 immer,但从文档和快速检查来看,produce 似乎为您创建了新对象,因此您并没有违反那里的规则。 (如果我将prevState[1] 保存到本地,然后在调用produce 之后将它与nextState[1] 进行比较,这是一个不同的对象——这是文档所说的你需要的,所以一切都很好。)
  • React 团队正在为基于函数的 react 编写文档,其中有一个部分简要介绍了相同的问题。 beta.reactjs.org/learn/….

标签: javascript reactjs react-hooks


【解决方案1】:

TL;DR - 您仍然需要进行深拷贝。

official docs 推荐深拷贝:

State 可以保存任何类型的 JavaScript 值,包括对象。但是你不应该直接改变你在 React 状态下持有的对象。相反,当您想要更新一个对象时,您需要创建一个新对象(或复制现有对象),然后设置状态以使用该副本。

他们还建议使您的状态对象尽可能平坦,以便更轻松地进行深度复制,如果这不是一个选项,请使用 immer 之类的东西来为您处理深度复制。

如果您的状态嵌套很深,您可能需要考虑将其展平。但是,如果你不想改变你的状态结构,你可能更喜欢嵌套传播的快捷方式。 Immer 是一个流行的库,可让您使用方便但可变的语法进行编写,并负责为您生成副本。

【讨论】:

  • 嘿@rangfu,我认为文档主要是在谈论顶级状态对象。因为即使immer(这绝对是官方推荐)也不会深度复制所有内容。它会检测到发生了什么变化,然后它会在对象树中重新创建所有内容。但例如,它不会在树中横向移动(例如:更改的节点的兄弟姐妹不会重新创建)。看看这个example。所以,IMO,不深度复制对象数组中的所有内容是 100% 可以的。
【解决方案2】:

不,它应该是这样工作的。是的,对象没有改变,但重要的是数组发生了变化。当事情发生变化时,React 会在 DOM 和虚拟 DOM 之间进行浅层比较。一旦你的新数组与旧数组进行比较,就会检测到更改,数组本身的内容并不重要。

【讨论】:

    【解决方案3】:

    是的,如果你想避免副作用,你必须进行深度克隆。

    在你的函数中:

    function handleImageClick(index) {
      props.setImages((prevState)=>{
        const aux = Array.from(prevState);
        aux[index].selected = !aux[index].selected;
        return aux;
      });
    }
    

    当您使用 Array.from 时,您将获得对存储对象的引用,因此您的 prevState 被修改。

    看这个:

    setTest(prevState => {
          console.log("PrevState", prevState);
          const b = Array.from(prevState);
          b[0].a = b[0].a + 1;
          return b;
        });
    

    你可以在这里测试它:

    https://codesandbox.io/embed/exciting-mcnulty-6spdh?fontsize=14

    您将看到 prevState 具有下一个值,因此您丢失了 prevState 值。

    假设您想在代码中执行此操作:

    function handleImageClick(index) {
          props.setImages((prevState)=>{
            const aux = Array.from(prevState);
            aux[index].selected = !aux[index].selected;
            aux[index + 1].selected = prevState[index].selected;
            return aux;
          });
        }
    

    aux[index + 1].selected = prevState[index].selected; 坏了!!

    所以你需要对数组进行深拷贝以避免这种事情。

    你可以这样做:const aux = JSON.parse(JSON.stringify(prevState));

    【讨论】:

    • 谢谢!我同意,如果我想访问刚刚更改的对象属性,则该信息将无法访问并且完全消失。但除此之外,您还看到其他问题吗?因为一旦setState() 完成运行,prevState 对象将不再可用。下一次,它将是一个不同的对象。 IE:我唯一担心的应该是当前正在进行的setState() 执行期间的prevState 值?
    • 因为如果这是唯一的问题,我认为不值得为了这种可能性而将所有内容作为一种习惯进行深度复制。我可以将该特定属性保存到辅助变量中,以便在更改代码后进一步访问代码。你怎么看?仅针对这种特定情况,是否值得深度复制所有内容?
    猜你喜欢
    • 2014-11-03
    • 2021-12-05
    • 2016-10-06
    • 2021-03-15
    • 2020-05-26
    • 2020-10-21
    • 1970-01-01
    • 2020-07-01
    • 2020-12-11
    相关资源
    最近更新 更多