'createSelector'如何接受'reselect'库中的输入参数?

时间:2017-04-14 02:32:59

标签: javascript reselect

我从reselect库中获取了以下代码。

使用subtotalSelector调用exampleState时,它会调用接受输入参数createSelector的函数exampleState

我的问题是createSelector如何接受exampleState以及其他功能消耗它?有一些隐含的参数发生,我不明白。

import { createSelector } from 'reselect'

const shopItemsSelector = state => state.shop.items
const taxPercentSelector = state => state.shop.taxPercent

const subtotalSelector = createSelector(
  shopItemsSelector,
  items => items.reduce((acc, item) => acc + item.value, 0)
)

const taxSelector = createSelector(
  subtotalSelector,
  taxPercentSelector,
  (subtotal, taxPercent) => subtotal * (taxPercent / 100)
)

export const totalSelector = createSelector(
  subtotalSelector,
  taxSelector,
  (subtotal, tax) => ({ total: subtotal + tax })
)

let exampleState = {
  shop: {
    taxPercent: 8,
    items: [
      { name: 'apple', value: 1.20 },
      { name: 'orange', value: 0.95 },
    ]
  }
}

console.log(subtotalSelector(exampleState)) // 2.15

通过替换输入参数,可以解释subtotalSelector

subtotalSelector = createSelector(
  state => state.shop.items,
  items => items.reduce((acc, item) => acc + item.value, 0)
)

subtotalSelector({
  shop: {
    taxPercent: 8,
    items: [
      { name: 'apple', value: 1.20 },
      { name: 'orange', value: 0.95 },
    ]
  }
});

1 个答案:

答案 0 :(得分:0)

我没有找到更好的方法来剖析此reselect library code,而是在函数中添加console.log。以下代码是从reselect库本身复制的。可以找到JSBin版本here

在控制台输出的底部,您可以看到exampleState实际上是代码中使用的arguments变量。这是一个JavaScript construct

function defaultEqualityCheck(a, b) {
  return a === b
}

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
}

function defaultMemoize(func, equalityCheck = defaultEqualityCheck) {
  
  let lastArgs = null
  let lastResult = null
  
  console.log("Entering defaultMemoize");
  
  console.log("###INPUT### defaultMemoize argument func type: " + typeof func);
  
  // 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 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(', ')
    throw new Error(
      'Selector creators expect all input-selectors to be functions, ' +
      `instead received the following types: [${dependencyTypes}]`
    )
  }

  return dependencies
}

function createSelectorCreator(memoize, ...memoizeOptions) {
  
  console.log("Entering createSelectorCreator");
  
  console.log("#INPUT# argument memoize name: " + memoize.name);

  console.log("#INPUT# argument memoize options: ");
  
  console.log(memoizeOptions);

  return (...funcs) => {
    
    let recomputations = 0
    
    const resultFunc = funcs.pop()
    const dependencies = getDependencies(funcs)

    console.log("##INPUT## argument funcs: ");
    console.log(resultFunc);
    
    const memoizedResultFunc = memoize(
      function () {
        recomputations++
        
        // apply arguments instead of spreading for performance.
        return resultFunc.apply(null, arguments)
      },
      ...memoizeOptions
    )
    
    console.log("memoizedResultFunc: " + typeof memoizedResultFunc);

    // If a selector is called with the exact same arguments we don't need to traverse our dependencies again.
    const selector = defaultMemoize(function () {
      const params = []
      const length = dependencies.length

      if (arguments != null)
      {
        console.log("***INPUT*** arguments: ");
        console.log(arguments);
      }
      
      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.
      return memoizedResultFunc.apply(null, params)
    })

    selector.resultFunc = resultFunc
    selector.recomputations = () => recomputations
    selector.resetRecomputations = () => recomputations = 0
    
    return selector
  }
}

const createSelector = createSelectorCreator(defaultMemoize)

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(
    objectKeys.map(key => selectors[key]),
    (...values) => {
      return values.reduce((composition, value, index) => {
        composition[objectKeys[index]] = value
        return composition
      }, {})
    }
  )
}

const shopItemsSelector = state => state.shop.items
const taxPercentSelector = state => state.shop.taxPercent

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

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 }

以下代码显示了与上述类似的功能组合示例。

function firstFunction() {
  console.log(arguments);
}

function secondFunction() {
  console.log(arguments);
}

function thirdFunction() {
  console.log(arguments);
}

function fourthFunction() {
  console.log(arguments);
  
  return function() {
    return function(x) { console.log(arguments); };
  }
}

const higherOrderFunction = fourthFunction(thirdFunction);

console.log("High Order Function");
console.log(higherOrderFunction);

const highestOrderFunction = higherOrderFunction(firstFunction, secondFunction);

console.log("Highest Order Function");
console.log(highestOrderFunction);

highestOrderFunction(10);