React 浅拷贝仍然会触发重新渲染?
React shallow copy still triggers re-render?
根据我对 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")
);
你可以试试上面的代码here
更新对象数组的正确方法应该如下所示(其他深度复制方法也可以)
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 立即更新,即使我只是更新原始项目对象?
渲染是由于 .map
创建了新数组。如果您在挂钩中执行类似 prev[1].name = "x"; return prev;
的操作,则不会执行更新。 Per reactjs doc on setState
with function argument:
If your update function returns the exact same value as the current
state, the subsequent rerender will be skipped completely.
更新.
是的,说到 parent-child 交互,item
是相同的(参考),但 child props
会有所不同。你有 MyItem({ item })
并且这个 item
正在从 props
解构,比如 MyItem(props)
,并且这个 props
改变是因为 parent 源改变了.
所以每次你 map
列表时,你明确地要求 parent 呈现它的 children,并且 [=41] 的一些(或全部)的事实=]的param没改也没关系。为了证明这一点,您可以从 child 组件中删除任何参数:
{items.map(() => ( <MyItem /> ))}
function MyItem () {
return <p>hello</p>
}
MyItem
将在您每次通过状态挂钩执行 items
更新时调用。而且它的 props
将始终与以前的版本不同。
如果您的 setItem 设置为 new object
,您的页面状态更改将重新呈现
按照您的逻辑,您执行了一个浅拷贝,其中:
- 浅拷贝创建了一个新对象,并从旧对象复制了第一层的所有内容。
浅拷贝和深拷贝也创建了一个新的对象,它们都触发了React中的重新渲染。
浅拷贝和深拷贝的区别在于:从旧对象的第2层开始,浅拷贝保持相同的对象,而深拷贝会在所有层创建新的对象。
根据我对 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")
);
你可以试试上面的代码here
更新对象数组的正确方法应该如下所示(其他深度复制方法也可以)
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 立即更新,即使我只是更新原始项目对象?
渲染是由于 .map
创建了新数组。如果您在挂钩中执行类似 prev[1].name = "x"; return prev;
的操作,则不会执行更新。 Per reactjs doc on setState
with function argument:
If your update function returns the exact same value as the current state, the subsequent rerender will be skipped completely.
更新.
是的,说到 parent-child 交互,item
是相同的(参考),但 child props
会有所不同。你有 MyItem({ item })
并且这个 item
正在从 props
解构,比如 MyItem(props)
,并且这个 props
改变是因为 parent 源改变了.
所以每次你 map
列表时,你明确地要求 parent 呈现它的 children,并且 [=41] 的一些(或全部)的事实=]的param没改也没关系。为了证明这一点,您可以从 child 组件中删除任何参数:
{items.map(() => ( <MyItem /> ))}
function MyItem () {
return <p>hello</p>
}
MyItem
将在您每次通过状态挂钩执行 items
更新时调用。而且它的 props
将始终与以前的版本不同。
如果您的 setItem 设置为 new object
,您的页面状态更改将重新呈现
按照您的逻辑,您执行了一个浅拷贝,其中:
- 浅拷贝创建了一个新对象,并从旧对象复制了第一层的所有内容。
浅拷贝和深拷贝也创建了一个新的对象,它们都触发了React中的重新渲染。
浅拷贝和深拷贝的区别在于:从旧对象的第2层开始,浅拷贝保持相同的对象,而深拷贝会在所有层创建新的对象。