在提到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
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时,都会进行计算,浪费性能。另外也违背了保持组件逻辑简单原则。数组
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 就是用来解决这个问题的,大体用法以下:
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能帮你省去这些不必的从新计算。
简单说,就是利用闭包(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 代码的转换器,欢迎你们查阅。