redux性能優化 - reselect

在提到reselect以前,咱們先看下面這個狀況。javascript

import React, { Component } from 'react'

class Demo extends Component {
    render() {
        const { a, b, c, uabc } = this.props
        return (
            <div> <h6>{a}</h6> <h6>{b}</h6> <h6>{c}</h6> <h6>{uabc}</h6> </div>
        )
    }
}

function u(x, y, z) {
    return 6*x + 9*y + 13*z
}
複製代碼

Demo組件收到的props:a, b, c, u(a, b, c)。關於 u(a, b, c)的計算,咱們應該放在哪裏?vue

將計算放在redux中

store的結構以下:java

store = {
    a:1,
    b:1,
    c:1,
    uabc: 28 // 6a + 9b + 13c
}
複製代碼

將計算u(a, b, c)放在reducer中,計算u(a, b, c)的代碼部分以下:react

switch(action.type) {
    case changeA: {
        return {
            ...state,
            a: action.a,
            uabc: u(action.a, state.b, state.c)
        }
    }
    case changeB: {
        ...
    }
}
複製代碼

這樣咱們的reducer 函數很是複雜了, 咱們每更新一個狀態值。 都得維護與這個值相關的值, 否則就會有數據不一致。git

爲了保證數據流清晰,咱們只把最基本的狀態存儲在redux。那麼咱們須要將u(a, b, c)的計算放在組件中。github

將計算放在組件中

能夠將計算放在render中:redux

class Demo extends Component {
    render() {
        const { a, b, c } = this.props
        const uabc = u(a, b, c)
        return (
            <div> <h6>{a}</h6> <h6>{b}</h6> <h6>{c}</h6> <h6>{uabc}</h6> </div>
        )
    }
}
複製代碼

這種狀況,當組件自身屬性ownProps,或者setState時,都會進行計算,浪費性能。另外也違背了保持組件邏輯簡單原則。數組

將計算放在mapStateToProps中

class Demo extends Component {
    render() {
        const { a, b, c, uabc } = this.props
        return (
            <div> <h6>{a}</h6> <h6>{b}</h6> <h6>{c}</h6> <h6>{uabc}</h6> </div>
        )
    }
}
function mapStateToProps(state) {
    const {a, b, c} = state
    return {
        a,
        b,
        c,
        uabc: u(a, b, c)
    }
}
Demo= connect(mapStateToProps)(Demo)
複製代碼

這種方式,組件只是接收數據展現便可。可是當store中的狀態改變時,會通知全部被connect且沒被銷燬的組件。緩存

若是頁面上有三個組件,這三個組件存在redux上的任意狀態的改變,都會觸發計算u(a, b, c)。react-router

但這一般不是問題,由於咱們通常每一個頁面只會有一個容器組件和redux進行關聯,其餘子組件都是經過props的方式獲取數據。當react-router切換路由時,是會銷燬前一個路由的組件。同一時間只會有一個容器組件。

不過另外一種狀況會致使無效計算u(a, b, c):

若是Demo組件還有另外一個狀態屬性d,也存在redux。這個屬性就是一個簡單的值,只用來展現。可當d變化時,也會觸發u(a, b, c)的計算。無論將計算放在render仍是mapStateToProps,都是沒法避免的。

精準控制計算

通過上面的討論,咱們得出了一些結論:

  • redux只存基本狀態

  • 一個路由儘可能單容器組件

可是平常開發中仍然會有相似屬性d,會致使相似u(a, b, c)的無效計算。咱們須要告訴程序:

只有a, b, c變化時,纔會計算u(a, b, c)。大概思路:

每次計算u(a, b, c)時,都會利用閉包原理緩存a, b, c 以及 計算的結果 result。當下一次調用這個函數時,會比對新舊入參a, b, c ,若是一致則直接返回以前緩存的結果 result,無需計算。大體代碼以下:

let memoizeState = null
function mapStateToProps(state) {
    const {a, b, c} = state
    // 若是沒有緩存則直接計算u(a, b, c)
    if (!memoizeState) { 
       memoizeState =  {
            a,
            b,
            c,
            uabc: u(a, b, c)
        }
    } else {
        // 若是a, b, c中任意一個變化,都會從新計算u(a, b, c)
        if (!(a === memoizeState.a && b === memoizeState.b && c === memoizeState.c) ) {
            memoizeState.uabc = u(a, b, c)
        }
        memoizeState.a = a
        memoizeState.b = b
        memoizeState.c = c
    }

    return memoizeState
}
複製代碼

神器登場 - reselect

reselect 就是用來解決這個問題的,大體用法以下:

import { createSelector } from 'reselect'

const aSelector = state => state.a
const bSelector = state => state.b
const cSelector = state => state.c

const uSelector = createSelector(
    [ aSelector, bSelector, cSelector ],
    (a, b, c) => u(a, b, c)
)

function mapStateToProps(state) {
    const { a, b, c } = state
    return {
        a,
        b,
        c,
        uabc: uSelector(state)
    }
}
複製代碼

mapStateToProps也被叫作selector, Reselect 提供 createSelector 函數來建立可記憶的 selector。createSelector 接收一個 input-selectors 數組和一個轉換函數做爲參數。

當 state tree 的改變會引發 input-selector 值變化,那麼 selector 會調用轉換函數,傳入 input-selectors 做爲參數,並返回結果。若是 input-selectors 的值和前一次的同樣,它將會直接返回前一次計算的數據,而不會再調用一次轉換函數。這樣就能夠避免沒必要要的計算,爲性能帶來提高。

結論

selector(即mapStateToProps)在store發生變化的時候就會被調用,而不論是不是selector關心的數據發生改變它都會被調用,因此若是selector計算量很是大,每次更新都從新計算可能會帶來性能問題。Reselect能幫你省去這些不必的從新計算。

補充:reselect記憶功能的原理

簡單說,就是利用閉包(closure),好比咱們把問題簡單化,讓createSelector就固定接受3個參數,代碼差很少就是下面這樣。

const createSelector = (selector1, selector2, resultSelector) => {
    let selectorCache1,  // selector1的結果記憶
        selectorCache2,  // selector2的結果記憶
        resultCache;     // resultSelector的結果記憶
    return (state) => {
        const subState1 = selector1(state);
        const subState2 = selector2(state);

        if (selectorCache1 === subState1 && selectorCache2 === subState2) {
            return resultCache;
        }
        selectorCache1 = subState1;
        selectorCache2 = subState2;     
        resultCache = resultSelector(selectorCache1, selectorCache2);
        return resultCache;
    };
}
複製代碼

用一個函數產生一個新的函數,前一個函數中的局部變量就能夠做爲新產生函數的「記憶體」。

另外最近正在寫一個編譯 Vue 代碼到 React 代碼的轉換器,歡迎你們查閱。

github.com/mcuking/vue…

相關文章
相關標籤/搜索