React redux - reselect

138 阅读2分钟

Reselect 是一个用于创建记忆的“selector”函数的库。

通常与 Redux 一起使用,但也适用于任何普通的 JS 不可变数据场景

  • selector 可以计算衍生数据,它允许让 Redux 存储尽可能少的 state
  • selector 很高效,它只有在某个参数发生变化时才会进行重新计算
  • selector 是可组合的,它可以作为其他 selector 的入参

我们可以用它包装数据(如 Redux 的 state),并利用其缓存入参的能力减少不必要的更新,从而达到性能优化,一举多得

1、简单的用例

createSelector(...inputSelectors | [inputSelectors], resultFunc)

  • inputSelectors 为 selector 或一个 selector 数组
  • resultFunc 为一个函数,接收前面 selector 计算出来的结果作为入参进行加工
import { createSelector } from 'reselect'

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


// 接受若干个 selector 或一个 selector 数组以及 `resultFunc` (最后一个)作为参数
const subtotalSelector = createSelector(
  shopItemsSelector,
  items => items.reduce((acc, item) => acc + item.value, 0)
)

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

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

// 导出
export default {
  subtotalSelector,
  taxSelector,
  totalSelector,
}
// 传入值
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 }

2、源码分析

// 全等比较 a b 两个参数
function defaultEqualityCheck(a, b) {
  return a === b
}

// 比较 prev 与 next 两组参数的差异
function areArgumentsShallowlyEqual(equalityCheck, prev, next) {
  if (prev === null || next === null || prev.length !== next.length) {
    return false
  }

  const length = prev.length
  for (let i = 0; i < length; i++) {
    if (!equalityCheck(prev[i], next[i])) {
      return false
    }
  }

  return true
}


// createSelector 函数的参数函数
export function defaultMemoize(func, equalityCheck = defaultEqualityCheck) {
  let lastArgs = null // 前一次参数
  let lastResult = null // 前一次执行结果
  // 借助闭包缓存前一次执行的参数与结果,仍返回一个函数
  return function () {
    if (!areArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) {
      lastResult = func.apply(null, arguments)
    }
    lastArgs = arguments
    return lastResult
  }
}

function getDependencies(funcs) {
  const dependencies = Array.isArray(funcs[0]) ? funcs[0] : funcs

  if (!dependencies.every(dep => typeof dep === 'function')) {
    const dependencyTypes = dependencies.map(
      dep => typeof dep
    ).join(', ')
  }

  return dependencies
}

export function createSelectorCreator(memoize, ...memoizeOptions) {

  // 向外导出的 createSelector 函数
  return (...funcs) => {
    // 重新计算的次数
    let recomputations = 0
    // 传入的 resultFunc 函数
    const resultFunc = funcs.pop()
    // 传入的 inputSelectors 函数
    const dependencies = getDependencies(funcs)

    // 根据上文函数签名,resultFunc 接收其他 selector 参数的计算结果作为参数 
    // 并使用记忆函数缓存入参,使用 recomputations 统计重新计算次数
    const memoizedResultFunc = memoize(
      function () {
        recomputations++
        return resultFunc.apply(null, arguments)
      },
      ...memoizeOptions
    )
    
    // 调用 inputSelectors
    const selector = memoize(function () {
      const params = []
      const length = dependencies.length

      // 计算依赖 selector 的结果,存入 params 作为 resultFunc 的入参
      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
  }
}

// 向外导出 createSelector 方法
export const createSelector = createSelectorCreator(defaultMemoize)

export function createStructuredSelector(selectors, selectorCreator = createSelector) {
  if (typeof selectors !== 'object') {
    throw new Error('error')
  }
  const objectKeys = Object.keys(selectors)
  return selectorCreator(
    objectKeys.map(key => selectors[key]),
    (...values) => {
      return values.reduce((composition, value, index) => {
        composition[objectKeys[index]] = value
        return composition
      }, {})
    }
  )
}

参考文档