Reselect是如何作到性能優化的?

reselect是什麼?

reselect是配合redux使用的一款輕量型的狀態選擇庫,目的在於當store中的state從新改變以後,使得局部未改變的狀態不會由於總體的state變化而所有從新渲染,功能有點相似於組件中的生命週期函數shouldComponentDidUpdate,可是它們並非一個東西。下面是官方的一些簡介:javascript

  • Selectors can compute derived data, allowing Redux to store the minimal possible state.
  • Selectors are efficient. A selector is not recomputed unless one of its arguments changes.
  • Selectors are composable. They can be used as input to other selectors.

[注]:並非reselect非要和redux綁定使用不可,能夠說reselect只是一個enhancement,並不表明強耦合。java

何時用reselect?

  • store狀態樹龐大且層次較深
  • 組件中的state須要通過複雜的計算才能呈如今界面上

我的認爲符合這兩點就可使用reselect,爲何?簡單的state或許根本徹底沒有必要引入redux,狀態管理組件內部就能夠消化,再者reselect只是在參數級別的緩存,若是組件狀態邏輯並非特別複雜,只是簡單的getter,那也可沒必要引入reselectgit

[建議]:建議引入了redux就能夠引入reselect,去看官方的源碼,總共加起來才短短的108行代碼,對測試並無什麼成本,同時加入也不會對打包體積形成什麼影響,可是有些時候對組件渲染的性能卻有很大的改善。github

基本用法

這裏是直接copy的官方倉庫中的代碼redux

Edit reselect

import { createSelector } from 'reselect'

const shopItemsSelector = state => state.shop.items
const taxPercentSelector = state => state.shop.taxPercent

const subtotalSelector = createSelector(
  shopItemsSelector,
  items => items.reduce((acc, item) => acc + item.value, 0)
)

const taxSelector = createSelector(
  subtotalSelector,
  taxPercentSelector,
  (subtotal, taxPercent) => subtotal * (taxPercent / 100)
)

export const totalSelector = createSelector(
  subtotalSelector,
  taxSelector,
  (subtotal, tax) => ({ total: subtotal + tax })
)

let exampleState = {
  shop: {
    taxPercent: 8,
    items: [
      { name: 'apple', value: 1.20 },
      { name: 'orange', value: 0.95 },
    ]
  }
}

console.log(subtotalSelector(exampleState)) // 2.15
console.log(taxSelector(exampleState))      // 0.172
console.log(totalSelector(exampleState))    // { total: 2.322 }
複製代碼

reselect是怎麼優化代碼性能的?

const selector = memoize(function () {
  const params = []
  const length = dependencies.length

  for (let i = 0; i < length; i++) {
    // apply arguments instead of spreading and mutate a local list of params for performance.
    params.push(dependencies[i].apply(null, arguments))
  }

  // apply arguments instead of spreading for performance.
  return memoizedResultFunc.apply(null, params)
})

selector.resultFunc = resultFunc
selector.dependencies = dependencies
selector.recomputations = () => recomputations
selector.resetRecomputations = () => recomputations = 0
return selector
複製代碼

函數總體返回的就是這個selector,由於咱們調用createSelector,其實返回的是一個函數,因此memoize返回的其實也是一個函數,那麼selector中作了什麼?緩存

export function defaultMemoize(func, equalityCheck = defaultEqualityCheck) {
  let lastArgs = null
  let lastResult = null
  // we reference arguments instead of spreading them for performance reasons
  // 這裏做爲返回的函數,傳入的參數即爲state
  return function () {
    if (!areArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) {
      // apply arguments instead of spreading for performance.
      lastResult = func.apply(null, arguments)
    }

    lastArgs = arguments
    return lastResult
  }
}
複製代碼

memoizereselect中提供的默認緩存函數,能夠的得知執行這個函數的時候,返回的函數即爲上面代碼中的selector,那麼arguments即爲傳入的state,經過areArgumentsShallowlyEqual比較兩次傳入的參數是否相等,注意,這裏是淺比較,即第一層引用的比較markdown

function defaultEqualityCheck(a, b) {
  return a === b
}
複製代碼

當兩次傳入的值存在變化的時候,那麼就會執行app

func.apply(null, arguments)
複製代碼

這裏會計算獲得全部的依賴,而後獲得下一輪緩存函數的paramsless

reduxreducer來說,這層緩存並無什麼做用,看看reducer代碼:ide

function reducer(state, action) {
  switch (action.type): 
    case REQUEST_TODO_PENDING:
    	return { ...state, loading: true };
  	case REQUEST_TODO_LIST_SUCCESS:
  		return { ...state, list: ['todo'], loading: false };
  	// ...
  	// default
}
複製代碼

redux社區推崇全部的state都是不可變的,因此只要dispatch了一個action,每次返回的state必然會是一個新的對象,對於淺比較每次返回的結果必然是true;

因此,緩存的關鍵還在第二層momoize,由於這裏的state並非每一次都必須變化:

const resultFunc = funcs.pop()
const dependencies = getDependencies(funcs)

const memoizedResultFunc = memoize(
  function () {
    recomputations++
    // apply arguments instead of spreading for performance.
    return resultFunc.apply(null, arguments)
  },
  ...memoizeOptions
)
複製代碼

真正代碼的執行在resultFunc.apply(null, arguments),這裏緩存的邏輯跟上面沒什麼區別,這裏就不在講解了。resultFunccreateSelector中的最後一個參數

const shopItemsSelector = state => state.shop.items
const taxPercentSelector = state => state.shop.taxPercent

const subtotalSelector = createSelector(
  shopItemsSelector,
  items => items.reduce((acc, item) => acc + item.value, 0)
)
複製代碼

你們能夠自行對照一下上面的這個例子,那麼arguments就是第二個函數的參數,也就是第一步緩存函數中的params

總結

好了,就囉嗦這麼多了,最後,多讀書,多看報,少吃零食,多睡覺😪😪💤

相關文章
相關標籤/搜索