首页 > 解决方案 > React 浅拷贝仍然触发重新渲染?

问题描述

根据我对 React 的了解,您不应该改变任何对象,否则 React 不知道重新渲染,例如,当单击按钮时,以下示例不应触发 UI 中的重新渲染:

import React, { useState } from "react";
import ReactDOM from "react-dom";

function App({ input }) {
  const [items, setItems] = useState(input);

  return (
    <div>
      {items.map((item) => (
        <MyItem item={item}/>
      ))}
      <button
        onClick={() => {
          setItems((prevItems) => {
            return prevItems.map((item) => {
              if (item.id === 2) {
                item.name = Math.random();
              }
              return item;
            });
          });
        }}
      >
        Update wouldn't work due to shallow copy
      </button>
    </div>
  );
}

function MyItem ({item}) {
  const name = item.name
  return <p>{name}</p>
}

ReactDOM.render(
  <App
    input={[
      { name: "apple", id: 1 },
      { name: "banana", id: 2 }
    ]}
  />,
  document.getElementById("container")
);

你可以在这里尝试上面的代码

并且更新对象数组的正确方法应该如下所示(其他深度复制方法也可以)

 setItems((prevItems) => {
    return prevItems.map((item) => {
               if (item.id === 2) {
                   # This way we return a deepcopy of item
                   return {...item, name: Math.random()}
               }
               return item;
            });
});

为什么即使我只是更新原始项目对象,第一个版本也可以正常工作并且 UI 会立即更新?

标签: javascriptreactjssetstate

解决方案


渲染是.map因为创建了新数组。如果您prev[1].name = "x"; return prev; 在钩子中执行类似操作,则不会执行更新。每个带有函数参数的reactjs doc :setState

如果您的更新函数返回与当前状态完全相同的值,则将完全跳过后续的重新渲染。

更新

是的,说到亲子互动,item将是相同的(通过引用),但孩子props会有所不同。你有MyItem({ item }),这item正在被解构props,比如MyItem(props),并且props由于父源发生了更改,因此发生了更改。

因此,每次map列出列表时,您都明确要求父级渲染其子级,而子级的部分(或全部)参数未更改这一事实并不重要。为了证明这一点,您可以从子组件中删除任何参数:

  {items.map(() => ( <MyItem /> ))}
function MyItem () {
  return <p>hello</p>
}

MyItemitems每次通过状态挂钩执行更新时都会调用。而且它props总是与以前的版本不同。


推荐阅读