Reselect源码解析

344 阅读2分钟

Reselect源码解析

原文:Reselect源码解析

Reselect主要为Redux的state数据提供了Memoize功能。它能够计算数据,传递最少且必要的state值;具有高性能,只在传递值改变时才会重新计算;具有可组合性,可以将它作为参数传递给其他selectors执行。

下面是个最基础的使用方式,可以跳转codepen来运行栗子

基础用法

createSelector

createSelector方法是Reselect的核心方法,最后一个参数默认为回调函数,其他参数可以为state、selector或数组,其中回调函数会引用其他参数作为自己的参数调用。

createSelector实际上调用的是Reselect中叫做createSelectorCreator的API,该API允许我们开发定制版本的createSelector

createSelectorCreator被调用时会引用默认的Memoize方法来校验输入数据,在源码中叫做defaultMemoize,该方法返回的函数就是用来判断是直接返回已经记忆的计算结果还是重新计算并返回新的结果。

// func就是createSelector参数中最后一个函数参数
// equalityCheck参数默认获取defaultEqualityCheck方法
// defaultEqualityCheck代码就是判断是否全等的函数:(a, b) => a === b
export function defaultMemoize(func, equalityCheck = defaultEqualityCheck) {
  let lastArgs = null
  let lastResult = null
  return function () {
    // 比较旧的和新的state是否全等
    // 如果不相等,则重新计算并将结果赋值给lastResult
    if (!areArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) {
      lastResult = func.apply(null, arguments)
    }

    lastArgs = arguments
    return lastResult
  }
}

上面代码中的areArgumentsShallowlyEqual方法思路很简洁,使用for循环依次比较传入的两个数组中的每一个值是否全等。

我们接下来看一下createSelectorCreator的代码是如何使用defaultMemoize的。

// memoize默认为上面介绍的defaultMemoize
// memoizeOptions作为memoize方法的引用参数
// 如果没有传值则默认为equalityCheck = defaultEqualityCheck
export function createSelectorCreator(memoize, ...memoizeOptions) {
  return (...funcs) => {
    // 统计计算次数
    let recomputations = 0
    // 获取selector参数中的最后一个回调函数
    const resultFunc = funcs.pop()
    // getDependencies函数获取其余的参数 先判断第一个参数是否为数组
    // 如果是则返回第一个数组 否则直接返回
    // 注意:参数中具体的项必须为函数类型,否则会抛出一个错误
    const dependencies = getDependencies(funcs)
    // 根据传入的arguments来判断是否重新计算结果
    const memoizedResultFunc = memoize(
      function () {
        recomputations++
        
        return resultFunc.apply(null, arguments)
      },
      ...memoizeOptions
    )

    // 根据传入的state是否改变来判断需不需要执行下面代码
    const selector = memoize(function () {
      const params = []
      const length = dependencies.length

      for (let i = 0; i < length; i++) {
        params.push(dependencies[i].apply(null, arguments))
      }

      return memoizedResultFunc.apply(null, params)
    })

    selector.resultFunc = resultFunc
    selector.dependencies = dependencies
    selector.recomputations = () => recomputations
    selector.resetRecomputations = () => recomputations = 0
    return selector
  }
}

总结

Reselect的源码很精简,阅读后能够提升对于Memoization和React优化的理解。作者在注释中多次说明使用for代替forEachevery,使用arguments而不是展开运算符是为了更快和提高性能。Reselect的官方文档阅读起来也非常的清晰和轻松,对于常见问题的解答也是很全面,这点要点赞。