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
}, {})
}
)
}