常被新手忽略的值赋值和引用赋值(偏redux向)

1,926 阅读3分钟

引言

在日常使用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的引用的对象不会被复制

尾语

如果你的项目中有引用赋值导致的问题,希望本文章会对你有所帮助~