我发现了一篇文章,其中指出,如果我想改变属性name
在这样的状态:
const [user, setUser] = useState({
name: 'Cody',
age: 25,
education: {
school: {
name: 'School of Code'
}
}
})
我需要做以下事情:
setUser(prevUser => {
return {
...prevUser,
education: {
...prevUser.education,
school : {
...prevUser.education.school,
name: 'Layercode Academy'
}
}
}
})
然而,他们后来表明,有可能使这个逻辑更简单,使用immerp .js(也改变useImmer上的useState),像这样:
setUser(draft => {
draft.education.school.name = 'Layercode Academy';
})
我的问题是我是否可以做到这一点,而不使用mer.js:
setUser(prevUser => {
const newUser = {...prevUser}
newUser.education.school.name = 'Layercode Academy'
return newUser
})
在我所见过的每个教程(不使用immerj .js)中,他们都做了解构。但是在很多情况下,给状态副本的属性赋值似乎更简单、更简洁。我没有直接设置state,只是修改copy,没有违反任何"规则"。。是否存在一些隐藏的陷阱?
这是两个副本之间的区别
浅拷贝
const a = { x: 0, y: { z: 0 } };
const b = {...a}; // or const b = Object.assign({}, a);
b.x = 1; // doesn't update a.x
b.y.z = 1; // also updates a.y.z
深拷贝
const a = { x: 0, y: { z: 0 } };
const b = JSON.parse(JSON.stringify(a));
b.y.z = 1; // doesn't update a.y.z
setState()不会立即改变它。状态,但会创建挂起的状态转换。访问。状态调用此方法后可能返回现有值。——丹
因此,考虑到这一点,您不必深度克隆所有内容,因为它可能非常昂贵并导致不必要的渲染。
所以如果你需要在渲染后处理数据
setUser(prevUser => {
return {
...prevUser,
education: {
...prevUser.education,
school : {
...prevUser.education.school,
name: 'Layercode Academy'
}
}
}
})
doSomething(user) // setUser merely schedules a state change, so your 'user' state may still have old value
}
相反,
setUser(prevUser => {
return {
...prevUser,
education: {
...prevUser.education,
school : {
...prevUser.education.school,
name: 'Layercode Academy'
}
}
}
}, (user) => { doSomething(user})
这样可以保证用户的更新,而不必担心突变。
是否有一些隐藏的陷阱?
是的,虽然我们不知道你的其他代码是否会遇到它们。所以一般来说就把它当作一个毯子"是的"。
你所做的是改变状态。如果整个操作只执行一次状态更新和重新呈现,则不太可能遇到问题。但它仍然建立在一个坏习惯的基础上。
在执行更多操作的情况下(多个批处理状态更新,在处理状态更新之前使用当前状态的其他逻辑等),那么您更有可能看到意想不到的(并且难以跟踪的)错误和行为。
"不改变状态"的总体规则这就是一个总的规则。确实存在一个可以改变状态而不会引起任何问题的情况。但是为什么呢?为什么仅仅因为问题还没有发生就依赖坏习惯呢?
状态更新应该是一个全新的对象(或数组,或只是普通值)。对于没有改变的对象属性,它仍然可以是对前一个对象属性的引用(解构会这样做)。但是对于任何改变了的属性,导致该属性的整个对象图都应该是新的。
是的,你的最后一个例子有问题。您仍然在修改原始用户的教育对象:
setUser(prevUser => {
const newUser = {...prevUser}
newUser.education.school.name = 'Layercode Academy'
// true
prevUser.education.school.name === newUser.education.school.name
return newUser;
});
您可以考虑将状态分解为多个状态(即原语):
const [userEducationSchoolName, setUserEducationSchoolName] = useState("School of Code");
但是很快就失控了。如果您的环境支持,您甚至可以使用structuredClone
:
setUser(prevUser => {
const newUser = structuredClone(prevUser);
newUser.education.school.name = 'Layercode Academy'
return newUser
});
,但由于一些浏览器不支持structuredClone
,你将需要一些其他的方式来深度克隆对象。这又把我们带回到伊默尔!Immer的全部意义在于为您提供状态的可变深层副本,供您更改。