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主要是實現函數緩存運行結果的功能,其代碼主要分爲如下幾塊