在提到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 代碼的轉換器,歡迎你們查閱。