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

mocdog發表於2018-04-24

引言

在日常使用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的引用的物件不會被複制

尾語

如果你的專案中有引用賦值導致的問題,希望本文章會對你有所幫助~
複製程式碼

相關文章