为何咱们须要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 变化的时候, 会执行计算。
并且这两种都违背了 咱们的基本原则: 保持组件逻辑简单spa

让数据逻辑离开组件!code

// 能够写成函数式组件
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是咱们不错的选择!

相关文章
相关标签/搜索