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)