1. 程式人生 > >如何有效地提高react渲染效率--深複製,淺複製,immutable原理

如何有效地提高react渲染效率--深複製,淺複製,immutable原理

1. 效能意義:保持state不變這個約束引導我們使用區域性更新物件的方法,這樣會可以非常有效地提高react或其他顯示框架的渲染效率。我們先來看看為了保持資料不變性,要怎麼對state做更新,以我們的蘋果籃子state為例:

例子:通知開始摘蘋果:apple/BEGIN_PICK_APPLE

為了保證每個版本的state不變性,我們有兩種實現方式:“深複製”,“淺複製”。我們來看兩種模式的內部原理:

深複製方式:有人會這樣想:“保持state的不變性很容易,只需要深複製一個state, 讓後在新的state要怎麼修改就怎麼修改,不ok了嗎?”,如下就是深複製

這種方式是一種很低階保持不變性的方式:

  1. 深複製操作執行效率低

  2. 沒有為渲染環節提供提高渲染效率的鋪墊

它只是簡單迎合保持資料不變性的約束,雖然有一定除錯意義,但是,不但沒有提高程式的效能,反而降低了程式的總體效能!沒有實踐意義。

淺複製方式:淺複製模式只對內部資料發生變化的引用做更新,如下

“state” 物件的內部資料發生變化,所以建立新的state引用;而apples array 內部資料不發生變化,所以就不對該引用做更新!在這個操作中,這種淺複製的方法執行效率比較高,而且其簡單地實現了資料不變性,為除錯帶來方便,同時,也是更重要的,這種淺複製的方式極大地提高了視覺元件渲染階段的執行效率!我們來對比一下:當用戶點選摘蘋果時,如果使用“深複製”,渲染程式需要重新遍歷整個state物件樹來做視覺更新,而使用淺複製來實現資料不變性時,渲染程式只需要遍歷state物件的一級子節點

即可,而不需要對apples array 做遍歷,效能大大地提高。尤其是當蘋果很多的時候,兩種方式的效能差距是非常明顯的。

備註:在react元件裡面,要開啟條件更新這個生命週期函式 shouldComponentUpdate, 才會對把這個效能提高點釋放出來,類似這樣:

...
shouldComponentUpdate(nextProps) {
    return nextProps.state != this.props.state;
}
...

下面我們再給出 “吃蘋果” reducer 進行淺複製的例子:

現在大家應該理解了用淺複製實現資料不變性的原理和意義了,下面我們來看具體的程式碼實現。

我們的程式碼用 es6 編寫,這裡要用到 es6 兩個非常方便的特性:

大家可以稍微看一下文件,或者看我下面的例子就知道其用法了:

// apple basket reducer

export default (state = {
    isPicking: false,
    newAppleId: 1,
    apples: [
        {
            id: 0,
            weight: 235,
            isEaten: false
        }
    ]
}, action) => {
    
    let newState ;
    

    switch (action.type) {
        case 'apple/BEGIN_PICK_APPLE':
            newState = Object.assign({}, state, {
                isPicking: true
            });
            return newState;

        case 'apple/DONE_PICK_APPLE':
            newState = Object.assign({}, state, {
                apples: [
                    ...state.apples,
                    {
                        id: state.newAppleId,
                        weight: action.payload,
                        isEaten: false
                    }
                ],
                newAppleId: state.newAppleId + 1,
                isPicking: false
            })
            return newState;

        case 'apple/FAIL_PICK_APPLE':
            //這裡只是簡單處理
            newState = Object.assign({}, state, {
                isPicking: false
            });
            return newState;

        case 'apple/EAT_APPLE':
            newState = Object.assign({}, state, {
                apples: [
                    ...state.apples.slice(0, action.payload),
                    Object.assign({}, state.apples[action.payload], { isEaten: true }),
                    ...state.apples.slice(action.payload + 1)
                ]
            })
            return newState;

        default:
            return state;
    }

};

大家會發現這種淺複製操作在開發的過程會複雜一點,於是就有了 immutable.js 這個專門處理不變性資料的庫(也是facebook出品),它可以使用類似賦值的方式生成淺複製的不變性資料,下面來看看它怎麼簡化我們的開發:

我們用 apple/EAT_APPLE 這個reducer來說明。

這是原來的 reducer:

...
case 'apple/EAT_APPLE':
    newState = Object.assign({}, state, {
        apples: [
            ...state.apples.slice(0, action.payload),
            Object.assign({}, state.apples[action.payload], { isEaten: true }),
            ...state.apples.slice(action.payload + 1)
        ]
    })
    return newState;
...

這是使用 immutable.js 庫的reducer :

import { fromJS } from 'immutable';
...
case 'apple/EAT_APPLE':
    return fromJS(state).setIn(['apples',action.payload,'isEaten'], true).toJS();
...

用了immutable.js後,輕鬆一行程式碼搞定!如果團隊約定 state 都用 immutable 內部的資料型別,就可以連 fromJS 和 toJS 的轉化都省了,超級方便!

到這裡, reducer 任務的介紹就結束啦~