reselect
version: 4.0.0
描述
reselect是能够缓存函数运行结果的一个缓存函数生成器,其接收一个函数返回一个新的具有缓存能力的函数
用途
- 用来缓存需要大量计算的结果
- 用来缓存频繁的重复运算的结果
解析
核心缓存函数,实现很简单
主要是把新旧参数是否不变的判断规则交给了equalityCheck
export function defaultMemoize(func, equalityCheck = defaultEqualityCheck) {
let lastArgs = null
let lastResult = null
// we reference arguments instead of spreading them for performance reasons
return function () {
if (!areArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) {
// apply arguments instead of spreading for performance.
lastResult = func.apply(null, arguments)
}
lastArgs = arguments
return lastResult
}
}
默认的比较函数只是定义了一个简单的规则,如果要深度对比对象或者数组,需要自行实现
function defaultEqualityCheck(a, b) {
return a === b
}
在判断新旧参数是否相同的过程中,需要对旧的参数进行遍历,将equalityCheck规则应用到每个参数的对比上
function areArgumentsShallowlyEqual(equalityCheck, prev, next) {
if (prev === null || next === null || prev.length !== next.length) {
return false
}
// Do this in a for loop (and not a `forEach` or an `every`) so we can determine equality as fast as possible.
const length = prev.length
for (let i = 0; i < length; i++) {
if (!equalityCheck(prev[i], next[i])) {
return false
}
}
return true
}
至此就已经获得了一个缓存函数生成器defaultMemoize,用法类似这样
function add(a, b) {
return a + b
}
let memoryAdd = defaultMemoize(add)
memoryAdd(1, 2)
// 直接返回结果
memoryAdd(1, 2)
但是reselect进行了进一步的扩展,定义了一个创建缓存函数的工厂,返回一个使用memoize来缓存结果的函数,而且该函数对传入的参数做了处理
export function createSelectorCreator(memoize, ...memoizeOptions) {
// 返回一个函数,该函数接收多个函数并最终返回selector
return (...funcs) => {
let recomputations = 0
// 最后一个函数用来计算结果
const resultFunc = funcs.pop()
// 除最后一个外的函数,都将作为参数传递给resultFunc
const dependencies = getDependencies(funcs)
// resultFunc的缓存函数
const memoizedResultFunc = memoize(
function () {
// 用来统计resultFunc真正被调用的次数
recomputations++
// apply arguments instead of spreading for performance.
return resultFunc.apply(null, arguments)
},
// 传递给memory,对于defaultMemoize来说可用来覆盖默认的defaultEqualityCheck比较规则
...memoizeOptions
)
// If a selector is called with the exact same arguments we don't need to traverse our dependencies again.
// selector是一个memoize生成的缓存函数,如果每次调用的参数相同则会直接返回结果
const selector = memoize(function () {
const params = []
const length = dependencies.length
// 当调用selector时将arguments传入每个dependencie,并将运行结果push进params里,然后传给resultFunc计算最终结果
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.
// 这里并没有直接调用resultFunc,而是调用对resultFunc进一步缓存得到的memoizedResultFunc,
// 也就是说如果selector调用时的参数arguments发生了改变,但是dependencies计算后的结果前后参数依然相等,则直接返回结果,
// 这里主要是处理arguments是引用类型时,可能基于数据不可变原则,arguments可能只是引用发生了改变,其内部的values并未发生改变,那么就可以直接返回ResultFunc的运行结果
return memoizedResultFunc.apply(null, params)
})
// 获得最终计算函数
selector.resultFunc = resultFunc
// 获得参数函数列表
selector.dependencies = dependencies
// 获取实时的计算次数
selector.recomputations = () => recomputations
// 重置recomputations
selector.resetRecomputations = () => recomputations = 0
return selector
}
}
所以createSelectorCreator的使用方式应该如下
export const createSelector = createSelectorCreator(defaultMemoize)
// createSelector返回的就是上面的selector
let memoryFunc = createSelector(
user => user.name,
user => user.age,
user => user.gender,
(name, age, gender) => [name, age, gender].join('')
)
// memoryFunc接收的参数会被传递给每个前置函数,比如user,也可以传递多个参数进去
let user = {
name: 'a',
age: 22,
type: 'male'
}
memoryFunc(user)
// 在react中此处的user就是redux的state
如果默认的===比较不满足需求,则可以自己实现一个比较函数并创建自己的createSelector函数
function customEqualityCheck(a, b) { // doSomething }
const createCustomSelector = createSelectorCreator(defaultMemoize, customEqualityCheck)
上面还有个getDependencies的函数主要是做参数处理
function getDependencies(funcs) {
// 从这里可以看出依赖项也可以放进一个数组中
const dependencies = Array.isArray(funcs[0]) ? funcs[0] : funcs
// 如果dependencies中存在不是函数的参数,则抛出异常
if (!dependencies.every(dep => typeof dep === 'function')) {
const dependencyTypes = dependencies.map(
dep => typeof dep
).join(', ')
throw new Error(
'Selector creators expect all input-selectors to be functions, ' +
`instead received the following types: [${dependencyTypes}]`
)
}
return dependencies
}
此外reselect还存在createStructuredSelector,是一个基于createSelector进一步封装用来结构化输出的函数,接受一个自定义结构的对象obj,其每个key对应的value都必须是个函数,返回结果是一个和obj具有相同key的对象,其每个key对应的值为obj上相应的key的value函数执行结果。
export function createStructuredSelector(selectors, selectorCreator = createSelector) {
if (typeof selectors !== 'object') {
throw new Error(
'createStructuredSelector expects first argument to be an object ' +
`where each property is a selector, instead received a ${typeof selectors}`
)
}
const objectKeys = Object.keys(selectors)
return selectorCreator(
// selector是个对象,selectorCreator接收的参数全部为函数,或者一个函数数组加一个函数,这里将selector的values放入数组中作为依赖参数
objectKeys.map(key => selectors[key]),
(...values) => {
// composition贯穿整个reduce遍历
return values.reduce((composition, value, index) => {
// 读取的objectKeys[index]也就是key值,value是依赖函数的执行结果
composition[objectKeys[index]] = value
return composition
}, {})
}
)
}
createStructuredSelector是相当于省略了最后一个函数,但是如果需要在最后一个函数中处理额外的逻辑,就不满足需求了,应该用createSelector
let selector = createStructuredSelector({
name: user => user.firstName + user.lastName,
age: user => user.age,
description: user => user.details.join(' '),
})
selector({
firstName: 'redux',
lastName: 'reselect',
age: 0,
details: [ 'oneoneone' ]
})
// 输出结构
=> {
name: 'reduxreselect',
age: 0,
description: 'oneoneone',
}
// 上面这段代码用createSelector来写是这样的
let selector = createSelector({
name: user => user.firstName + user.secondName,
age: user => user.age,
description: user => user.details.join(' '),
(name, age, description) => { name, age, description }
})
理解
- createSelectorCreator出现的目的是什么
在createSelectorCreator中主要的功能是定制参数和再次缓存resultFunc,如果按照只是需要一个能缓存值的函数defaultMemoize已经达到了需求,而createSelectorCreator中能够接受多个函数,前面所有函数作为最后一个函数的参数,
为何这样设计
如果是用defaultMemoize,代码可能如下
const state = {}
let memoriedFuncA = defaultMemoize(function(state) {
// get taskC
// todo taskA
return ''
})
let memoriedFuncB = defaultMemoize(function(state) {
// get taskC
// todo taskB
return ''
})
// 上面的代码很明显可以将公共模块分离出来
let memoriedFuncC = defaultMemoize(function(state) {
// todo taskC
return ''
})
let memoriedFuncA = defaultMemoize(function(state) {
let resultc = memoriedFuncC(state)
// todo taskA
return ''
})
let memoriedFuncB = defaultMemoize(function(state) {
let resultc = memoriedFuncC(state)
// todo taskB
return ''
})
但是这样memoriedFuncC是硬编码到memoriedFuncA、memoriedFuncB中,考虑耦合问题可以将memoriedFuncC作为参数传入,而memoriedFuncC本身是个函数,并且可能存在多个依赖,自然就想到传入多个函数
let memoriedFuncB = defaultMemoize(function(state, taskC) {
let resultc = taskC(state)
// todo taskB
return ''
})
// createSelectorCreator对参数进行了整理
let memoriedFuncC = createSelector(
state => 'c'
)
let memoriedFuncA = createSelector(
memoriedFuncC,
c => 'a'
)
let memoriedFuncB = createSelector(
memoriedFuncC,
c => 'b'
)
总结
reselect主要是实现函数缓存运行结果的功能,其代码主要分为以下几块
- 缓存函数defaultMemoize,判断前后参数是否相等来决定执行还是直接返回结果
- 分离出前后参数对比的逻辑areArgumentsShallowlyEqual
- 分理出判断是否相等的逻辑defaultEqualityCheck
- 定义工厂createSelectorCreator,定制了参数格式也就是调用方式
- 基于createSelector对结构化输出进行了封装(createStructuredSelector)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。