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

总结

好了,就啰嗦这么多了,最后,多读书,多看报,少吃零食,多睡觉😪😪💤

相关文章
相关标签/搜索