在功能组件中更新状态时进行浅拷贝有哪些风险或副作用?

What are risks or side effects of doing shallow copy when updating state in functional component?

我有下面这个小例子,我假设如果我在替换状态之前做一个浅拷贝,我的会话属性将不会是新的,而只是对旧的引用。我将浅拷贝定义为变量 newSpeakersDataShallow ,将深拷贝变量命名为 newSpeakersDataDeep.

如果您更改传递给 setSpeakerData 的内容,您会发现它们的工作原理相同。我的问题是,我的风险是什么?如果我知道我不会更改会话数组,可以使用浅拷贝吗?

(这只是一个简化的示例,所以答案不是,还不如使用深度,因为在这种情况下很容易做到)。

https://codesandbox.io/s/clever-varahamihira-ry9x4?file=/pages/index.js:0-1990

与codesandbox相同的代码:

import { useEffect, useState } from "react";

const data = [
  {
    id: "1",
    first: "Joe",
    last: "Smith",
    favorite: true,
    sessions: [
      {
        id: "32",
        title: "Rails powered by",
      },
      {
        id: "58",
        title: "Hello World to .NET 3.5 interoperable Web service",
      },
    ],
  },
  {
    id: "2",
    first: "Jon",
    last: "Jones",
    favorite: false,
    sessions: [
      {
        id: "1011",
        title: "scalability and deployability",
      },
    ],
  },
  {
    id: "3",
    first: "Sam",
    last: "Hulk",
    favorite: true,
    sessions: [],
  },
];

function Speakers() {
  const [speakersData, setSpeakersData] = useState([]);

  useEffect(() => {
    setSpeakersData(data);
  }, []);

  return (
      <>
        {speakersData.map(function (speaker) {
          return (
              <div key={speaker.id}>
                <button
                    onClick={() => {
                      const newSpeakersDataShallow = speakersData.map(function (rec) {
                        return speaker.id == rec.id
                            ? { ...rec, favorite: !rec.favorite }
                            : { ...rec };
                      });

                      const newSpeakersDataDeep = speakersData.map(function (rec) {
                        return speaker.id == rec.id
                            ? {
                              ...rec,
                              favorite: !rec.favorite,
                              sessions: [...rec.sessions],
                            }
                            : { ...rec };
                      });

                      // RESULTS ARE SAME WHETHER DEEP OR SHALLOW COPY USED
                      setSpeakersData(newSpeakersDataDeep);
                    }}
                >
                  Toggle Favorite
                </button>
                &nbsp;{speaker.id} {speaker.first} {speaker.last}{" "}
                {JSON.stringify(speaker.sessions)}{" "}
                {speaker.favorite === true ? "true" : "false"}
              </div>
          );
        })}
      </>
  );
}

export default Speakers;

如果不以任何方式操作数据,则可以使用浅拷贝。

仅当您正在执行的操作更改数据时才存在使用浅拷贝的风险,因为这将导致绕过反应状态的直接突变setState,这可能会导致副作用,例如状态与渲染结果不匹配。

对于深度克隆,您可以使用像 lodash.cloneDeep 这样的库来避免手动处理对象。

当更新状态时,您将只为您更新的对象制作新副本的最佳方式,而其他人则保持不变。保留旧引用不会对您状态中未更改的部分产生任何影响,无需复制它们。

您的目标是避免改变状态的各个部分。通过这种方式,您可以确保您的更新状态正确反映而不会出现意外行为。不需要创建深层副本,对于深层嵌套状态来说成本可能很高。

你的第一个例子与我的方法很接近:

  • map 创建一个新数组,这样你就不会改变它;
  • 您为需要更改的对象创建了浅拷贝,并正确更新了所需的值;

不同之处在于未触及的对象不需要 return 新副本 {...rec}。鉴于它未被触及,您可以安全地 return 相同的对象,避免新对象的成本。


const newSpeakersDataShallow = speakersData.map(function (rec) {
  return speaker.id == rec.id
      ? { ...rec, favorite: !rec.favorite }
      : rec;
});