引言
在日常使用redux的时候,当view发出一个action请求并被对应的reducer处理时,有时候store中对应的state值已经变了,但view中未进入componentWillReceiveProps这个生命周期。本文主要解释下这种现象的发生到底是因为什么。
值引用
简单值(即标量基本类型值),包含的数据类型有null,undefined,字符串,数字,布尔值和ES6中的symbol。
例子
const a = 1;
let b = a;
b === a // true
特点:被赋值的变量改变时,赋值变量的值不会随之改变
const nickname = 'ssx';
let lastname = nickname;
lastname === nickname // true;
lastname = 'Mr.oldwang';
nickname //'ssx'
引用赋值
针对复合对象(数组,对象,函数)
例子
- 对象
const user1 = { nickname: 'ssx', age: 23 }
let user2 = user1;
user2; // { nickname: 'ssx', age: 23 }
user1 === user 2; // true
user2.nickname = 'gdd';
user1.nickname = 'gdd';
- 数组
const userIdlist = [1,2,3,4];
let newUserIdlist = userIdlist;
newUserIdlist === userIdlist; // false
newUserIdlist[0] = 2;
newUserIdlist; // [2,2,3,4]
userIdlist; // [2,2,3,4]
如果数据结构随着项目,复杂度也随之提升。引用赋值可能会导致预期效果和实际不一致。
针对实际React项目谈谈引用赋值带来的问题
1.redux的listening方法不会被触发,从而不会执行mapStateToProps, view不会刷新
2.不当的引用赋值导致的未dispatch一个action就已经修改了对应store中的值
分别根据问题,写几个实际例子
针对1中的例子
// info = {id: 10023, nickname: 'ssx_real', age: 40}
function userlistReducer(
state = [ {id: 10023, nickname: 'ssx', age: 23} ],
action ={}
) {
const { type, info } = action;
case "GET_USERINFO_LOADING":
return state;
case "GETREAL_USERINFO_SUCCESS":
{
const userlist = state;
const fakeUserinfo = userlist.find(u => u.id === info.id);
if (fakeUserinfo) {
fakeUserinfo.nickname = info.nickname;
fakeUserinfo.age = info.age;
}
return [...userlist];
}
case ... // 其他省略
default: return state;
}
上面这种写法导致的现象是想要将原来fake的数据修改成通过网络获取的真数据,但在return之前就已经修改了目标对象的值,即使dispatch后store中对应值修改了,也不是我们想要的效果。
prevprops userlist: [{id: 10023, nickname: 'ssx_real', age: 40}]
action: GETREAL_USERINFO_SUCCESS
nextprops userlist: [{id: 10023, nickname: 'ssx_real', age: 40}]
改写
// realInfo = {id: 10023, nickname: 'ssx_real', age: 40}
function userlistReducer(
state = [ {id: 10023, nickname: 'ssx', age: 23} ],
action ={}
) {
const { type, realInfo } = action;
case "GET_USERINFO_LOADING":
return state;
case "GETREAL_USERINFO_SUCCESS":
{
const userlist = state;
const fakeUserinfo = {...userlist.find(u => u.id === realInfo.id)};
if (fakeUserinfo) {
fakeUserinfo.nickname = realInfo.nickname;
fakeUserinfo.age = realInfo.age;
}
return [
...userlist.filter(u => u.id !== realInfo.id),
...[fakeUserinfo]
]
}
case ... // 其他省略
default: return state;
}
针对2中的例子
componentWillReceiveProps(nextProps) {
// todo something...
for (let item of newProps.groupRooms[this.state.roomID]) {
let item = {};
item.obj = item
item.obj.lastRecord = nextProps.chatData.chatlist.lastRecord
roomsData.push(item);
}
this.setState({ roomsData });
// todo something...
}
上面的例子本意没打算修改对应groupRooms[this.state.roomID]的值 但是由于用了引用赋值导致间接修改其值。这就是上面说的第2中情况,未dispatch一个action就已经修改了对应store中的值。
改写
componentWillReceiveProps(nextProps) {
// todo something...
for (let item of newProps.groupRooms[this.state.roomID]) {
let item = {};
item.obj = {...item};
item.obj.lastRecord = {...nextProps.chatData.chatlist.lastRecord}
roomsData.push(item);
}
this.setState({ roomsData });
// todo something...
}
关于根据不同数据类型解决引用赋值的一些方法:
- es6 扩展运算符
... - 数组
concat()[...arr1,...arr2]arr1 = new Array(...arr2) - 对象
Object.assign({}, obj1)obj2 = {...obj1} map集合m1 = new Map() m2.forEach((v,k) => m1.set(k, v))
这些方法主要是进行了浅copy,对象的key被copy时,key的引用的对象不会被复制
尾语
如果你的项目中有引用赋值导致的问题,希望本文章会对你有所帮助~