问题
使用useState 时,例如
const [selectedId, setSelectedId] = useState([false,true,false]);
这样直接利用,无法引发使用selectedId状态的组件的变化,但是selectedId是修改了的
let temp=selectedId;
temp[toggledId]=selectedId[toggledId]===false?true:false;
setSelectedId(temp);
原因
一句话。let temp=selectedId;
没有创建新的数组
这实际上是将 temp 设置为 selectedId 的引用,而不是一个新的数组。这意味着 temp 和 selectedId 共享相同的内存地址,它们指向相同的数组。所以,当你修改 temp 时,也会影响到 selectedId。
你直接修改了 selectedId 数组的值,React无法检测到setSelectedId前后状态的变化,因此不会触发 React 组件的重新渲染。
下面这个方法也不推荐,因为虽然 updatedSelectedId 这个数组是新的,但是其内部的元素本身与原数组 selectedId是相同的。因此,修改 updatedSelectedId[toggledId],其实是在修改原始的 selectedId对象。这样虽然会触发渲染,但是如果此时有另一个地方有着相同的初始 state,他们的 state 会被共享,也就是说你把本不该改变的状态也改变了。
// 创建一个新的数组,保持不可变性
const updatedSelectedId = [...selectedId];
updatedSelectedId[toggledId] = !updatedSelectedId[toggledId];
// 更新状态
setSelectedId(updatedSelectedId);
解决
我们在更新state时要将state视为不可变的,你不应该使用类似于 arr[0] = ‘bird’ 这样的方式来重新分配数组中的元素,也不应该使用会直接修改原始数组的方法,例如 push() 和 pop()。可以通过使用像 filter() 和 map()
这样不会直接修改原始值的方法,从原始数组生成一个新的数组。
其中数组展开运算符…还允许你把新添加的元素放在原始的 …artists 之前。如此,展开操作就可以完成 push() 和 unshift() 的工作,将新元素添加到数组的末尾和开头。
因此这里我们通过map来产生新的数组
setMyList(selectedId.map((item,id)=> {
if (id === toggledId) {
return !item;
} else {
return item;
}
}));
...
展开语法本质是是**“浅拷贝”——它只会复制一层**。这使得它的执行速度很快,但是也意味着当你想要更新一个嵌套属性时,你必须得多次使用展开语法。
为什么在 React 中不推荐直接修改 state?
更新 state 中的数组
即使你拷贝了数组,你还是不能直接修改其内部的元素。这是因为数组的拷贝是浅拷贝——新的数组中依然保留了与原始数组相同的元素。因此,如果你修改了拷贝数组内部的某个对象,其实你正在直接修改当前的 state。
const nextList = [...list];
nextList[0].seen = true; // 问题:直接修改了 list[0] 的值
setList(nextList);
nextList 和 list 是两个不同的数组,nextList[0] 和 list[0] 却指向了同一个对象。因此,通过改变 nextList[0].seen,list[0].seen 的值也被改变了。
解决:可以使用 map
在没有 mutation 的前提下将一个旧的元素替换成更新的版本。
setMyList(myList.map(artwork => {
if (artwork.id === artworkId) {
// 创建包含变更的*新*对象
return { ...artwork, seen: nextSeen };
} else {
// 没有变更
return artwork;
}
}));