version: 4.0.0react
reselect是可以缓存函数运行结果的一个缓存函数生成器,其接收一个函数返回一个新的具备缓存能力的函数redux
核心缓存函数,实现很简单
主要是把新旧参数是否不变的判断规则交给了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规则应用到每一个参数的对比上app
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来缓存结果的函数,并且该函数对传入的参数作了处理oop
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的使用方式应该以下this
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中主要的功能是定制参数和再次缓存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主要是实现函数缓存运行结果的功能,其代码主要分为如下几块