爲何咱們須要reselect

爲何咱們須要reselect

遇到的問題

先看下下面的一個組件javascript

import React, { Component } from 'react'
import { connect } from 'react-redux'

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

function f(x, y) {
    return a + b
}

function h(x, y) {
    return x + 2 * y
}

function g(x, y) {
    return 2 * x + y
}

function u(x, y, z) {
    return x + y + z
}

這個UnusedComp 組件關心這樣的幾個props: a, b, c, f(a,b), h(b, c), g(a, c), u(a, b, c), 其中f, h, g, u分別是一個函數。 關於這幾個計算的值, 咱們應該怎麼處理呢?java

把數據直接計算在redux

第一種, 咱們把全部值存在redux, 全部store的結構大概是這樣的:react

store = {
    a:1,
    b:1,
    c:1,
    fab: 2, // a + b
    hbc: 3, // b + 2c
    gac: 3, // 2a + c
    uabc: 3 // a + b + c
}

這樣咱們的組件簡單了, 只須要直接取值渲染就好 const { a, b, c, fab, hbc, gac, uabc } = this.props 。 那麼問題來了, reducer的函數應該怎麼處理呢? 對應的以下:redux

switch(action.type) {
    case 'changeA': {
        return {
            ...state,
            a: action.a,
            fab: f(action.a, state.b),
            gac: g(action.a, state.c)
            uabc: u(action.a, state.b, state.c)
        }
    }
    case 'changeB': {
        ...
    }
    case 'changeC': {
        ...
    }
}

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

reducer 只存最基本狀態

爲了保證數據流的清晰, 更新的簡單。 咱們只把最基本的狀態存儲在redux。store的結構和redcuer函數以下:函數

store = {
    a:1,
    b:1,
    c:1,
}
...
switch(action.type) {
    case 'changeA': {
        return {
            ...state,
            a: action.a
        }
    }
    ...
}

此刻組件多是這樣的:性能

class UnusedComp extends Component {
    render() {
        const { a, b, c } = this.props
        const fab = f(a, b)
        const hbc = h(b, c)
        const gac = g(a, c)
        const uabc = u(a, b, c)
        return (
            <div>
                <h6>{a}</h6>
                <h6>{b}</h6>
                <h6>{c}</h6>
                <h6>{fab}</h6>
                <h6>{hbc}</h6>
                <h6>{gac}</h6>
                <h6>{uabc}</h6>
            </div>
        )
    }
}

或者這樣的:this

class UnusedComp extends Component {
    componentWillReciveProps(nextProps) {
        const { a, b, c } = this.props
        this.fab = f(a, b)
        this.hbc = h(b, c)
        this.gac = g(a, c)
        this.uabc = u(a, b, c)
    }
    

    render() {
        const { a, b, c } = this.props
        return (
            <div>
                <h6>{a}</h6>
                <h6>{b}</h6>
                <h6>{c}</h6>
                <h6>{this.fab}</h6>
                <h6>{this.hbc}</h6>
                <h6>{this.gac}</h6>
                <h6>{this.uabc}</h6>
            </div>
        )
    }
}

對於第一種狀況, 當組件ownProps(組件自身屬性, 非redux傳遞), 或者setState 的時候 都會執行計算。
對於第二鍾狀況, 當組件ownProps 變化的時候, 會執行計算。
並且這兩種都違背了 咱們的基本原則: 保持組件邏輯簡單code

讓數據邏輯離開組件!component

// 能夠寫成函數式組件
class UnusedComp extends Component {
    render() {
        const { a, b, c, fab, hbc, gac, uabc } = this.props
        return (
            <div>
                <h6>{a}</h6>
                <h6>{b}</h6>
                <h6>{c}</h6>
                <h6>{fab}</h6>
                <h6>{hbc}</h6>
                <h6>{gac}</h6>
                <h6>{uabc}</h6>
            </div>
        )
    }
}
function mapStateToProps(state) {
    const {a, b, c} = state
    return {
        a,
        b,
        c,
        fab: f(a,b),
        hbc: h(b,c),
        gac: g(a,c),
        uabc: u(a, b, c)
    }
}
UnusedComp = connect(mapStateToProps)(UnusedComp)

組件很簡單, 接收數據展現就能夠了。 看似很美好! 咱們知道當store數據被改變的時候, 會通知全部connect的組件(前提是沒被銷燬)。
全部假設頁面上還有 A, B, C三個組件, 這三個組件任意狀態(存在redux的狀態)的改變, 都會出發這裏的 f, h, g, u的執行。。。聽起來很扯!!!的確很扯!(在redner裏面, willReciveProps裏面計算是這裏是不會引發函數執行的)。 可是這一般不是問題, 由於咱們通常每一個頁面只有一個 容器組件 和redux交互, 其餘子組件經過props的方式獲取數據和action。 並且react-router在切換路由的時候, 是會銷燬掉前一個路由的組件。 這樣同一個時間只會有 一個 容器組件。

在考慮一種狀況, 假設UnusedComp還有 x, y, z 狀態屬性, 存在redux。 這3個屬性就是簡單的3個值, 只用來展現。 但是當x, y, z改變的時候,也會觸發計算。 這裏發生的計算不論是在render裏面計算, 仍是willReciveProps, 仍是mapStateToProps裏 都沒法避免。

精確控制計算

彷彿咱們依據找到了 方法:

  1. redux只存基本狀態

  2. react-router + 單 容器組件 組件

現實很殘酷! 實際上x, y, z這種屬性, 必定大量存在。 光是這一點就會致使大量的無效計算。 以前討論的3種方式 (render, willRecive,mapStateToProps)沒法避免這種計算。

另外mapStateToProps 還會被其餘store的值改變影響 ,畢竟react-router + 單 容器組件 組件 這種組織方式只是最美好的狀況。 咱們有些業務就是處於性能的考慮,沒有銷燬以前路由的組件, 用咱們本身的路由。有些頁面也不是 單容器組件,尷尬!!

明顯的, 咱們是知道 x, y, z的變化是不須要計算的, 而a,b, c變化是須要計算的。 如何描述給程序呢?另外 mapStateToProps 這種方式還帶來了好處, 咱們在描述的時候,不會侵入組件!!。

最原始的描述:

let memoizeState = null
function mapStateToProps(state) {
    const {a, b, c} = state
    if (!memoizeState) { 
       memoizeState =  {
            a,
            b,
            c,
            fab: f(a,b),
            hbc: h(b,c),
            gac: g(a,c),
            uabc: u(a, b, c)
        }
    } else {
        if (!(a === memoizeState.a && b === memoizeState.b) ) {
            // f should invoke
            memoizeState.fab = f(a, b)
        }
        if (!(b === memoizeState.b && c === memoizeState.c) ) {
            // h should invoke
            memoizeState.hbc = h(b, c)
        }
        if (!(a === memoizeState.a && c === memoizeState.c) ) {
            // g should invoke
            memoizeState.gac = g(a, c)
        }
        if (!(a === memoizeState.a && b === memoizeState.b && c === memoizeState.c) ) {
            // u should invoke
            memoizeState.uabc = u(a, b, c)
        }
        memoizeState.a = a
        memoizeState.b = b
        memoizeState.c = c
    }
    
    return memoizeState
}

首選, 咱們知道fab的值與a,b 有關, 因此當a, b 有變化的時候,f須要從新執行。 其餘同理, 這樣的話函數必定是隻在必要的時候執行。

使用reselect

reselect 解決了咱們上面的那個問題, 咱們也沒必要每次用這個最原始的描述了, 對應的reselect描述是這樣的

import { createSelector } from 'reselect'

fSelector = createSelector(
    a => state.a,
    b => state.b,
    (a, b) => f(a, b)
)
hSelector = createSelector(
    b => state.b,
    c => state.c,
    (b, c) => h(b, c)
)
gSelector =  createSelector(
    a => state.a,
    c => state.c,
    (a, c) => g(a, c)
)
uSelector = createSelector(
    a => state.a,
    b => state.b,
    c => state.c,
    (a, b, c) => u(a, b, c)
)

...
function mapStateToProps(state) {
    const { a, b, c } = state
    return {
        a,
        b,
        c,
        fab: fSelector(state),
        hbc: hSelector(state),
        gac: gSelector(state),
        uabc: uSelector(state)
    }
}

在 createSelector 裏面咱們先定義了 input-selector 函數, 最後定義了 值是如何計算出來的。 selector保證了,當input-selector 返回結果相等的時候,不會計算。

最後

若是 你是react-router 而且是 單 容器組件。 那麼可能在 mapStateToProps裏面計算,性能問題並不大。 並且性能不該該是咱們第一要考慮的東西, 咱們首先要考慮的是簡單性,尤爲是組件的簡單性。 當咱們的業務複雜到須要考慮性能的時候, reselect是咱們不錯的選擇!

相關文章
相關標籤/搜索