根据我对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立即更新,即使我只是更新原来的项目对象?
由于.map
创建新数组而发生渲染。如果在钩子中执行prev[1].name = "x"; return prev;
之类的操作,则不会执行更新。根据reactjs文档对setState
的函数参数:
如果您的更新函数返回与当前相同的值状态时,将完全跳过后续呈现。
更新。
是的,说到亲子互动,item
是一样的(参考),但孩子props
不同。你有MyItem({ item })
,这个item
是从props
中析构出来的,比如MyItem(props)
,这个props
因为父源的改变而改变。
因此,每次您map
列表时,您都显式地要求父节点呈现其子节点,并且子节点的一些(或全部)参数没有改变的事实并不重要。为了证明这一点,你可以从子组件中删除任何参数:
{items.map(() => ( <MyItem /> ))}
function MyItem () {
return <p>hello</p>
}
MyItem
将在每次通过状态钩子执行items
更新时被调用。并且它的props
将永远不同于以前的版本。
如果你的setItem设置为new object
,你的页面状态改变将重新呈现在您的逻辑中,您执行了一个浅复制,其:
- 浅拷贝创建一个新对象,并从旧对象复制第一级的所有内容。
浅复制和深复制也创建了一个新对象,它们都会触发React中的重新渲染。
浅拷贝和深拷贝的不同之处在于:从旧对象的第2层开始,浅拷贝保持相同的对象,深拷贝将在所有层创建新对象。