第一个栗子🌰
state = {
name: 'Sam'
}
fn() {
const { data } = this.state;
data.name = 'xiaobe';
this.setState({
data: { ...data }
})
}
没有出现警告,且能实现效果,但这样的做法合理吗?
第二个栗子🌰:
使用了lodash/find
,重新赋值了一个新的数组,但是原数组依旧被更改了,为什么?
import find from 'lodash/find';
state = {
data: [{
id: 1,
name: 'xiaobe'
}, {
id: 2,
name: 'Sam'
}]
}
fn() {
const { data } = this.state;
const newData = [...data];
console.log('修改前 data', data);
const newItem = find(newData, { id: 1 }) || {};
console.log('修改前 newItem', newItem);
if (Object.keys(newItem).length) {
newItem.name = 'new-xiaobe';
}
console.log('修改后 newItem', newItem);
this.setState(
{
data: [...newData],
},
() => {
console.log('修改后 data', data);
},
);
}
发现,修改前 data里第一条name已经从xiaobe
变成了new-xiaobe
,请问为什么?
在回答问题之前,先补充以下几个知识点:
第一个栗子:
首先这样子的写法是没有问题,react调度的时候会判断oldData和newData是否相等,不相等时则更新视图,引用类型之间的比较实际就是内存地址的比较,如果内存地址是一样则判断结果为true,不一样则判断结果为false。这里的{...data},相当于重新开辟了内存地址,newData相比较oldData,内存地址发生了改变,那么react便会知道其发生了改变,然后去更新视图。但这种写法并不是最好的,因为毕竟是对原数据做了修改,而this.setState又是异步的,如果在this.setState之后紧跟着依据data又做了其他相应的操作,那么就会有问题。我个人推荐的写法应该是这样子的:
第二个栗子:
理解这个问题的核心就是得了解[...data]到底干了啥?下面以你提供的数据来解释以下:
为了方便理解,我改造一下data。
[...data]的流程如下:
通过上面的代码,我们发现如果直接判断newData===data,结果一定为false,因为newData的内存地址和data的不一致。但是如果你判断newData[0]===data[0]以及newData[1]===data[1],那结果变为true,子项的内存地址并没有发生变化。当你修改第一项的name值之后,便会影响到data和newData,因为这个子项被data和newData都引用了。