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

How 'createSelector' is accepting input parameter in 'reselect' library?

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

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

我的问题是 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 },
    ]
  }
});

我没有找到更好的方法来剖析这个 reselect library code, but to add console.log inside the functions. The following code is copied from the reselect library itself. The JSBin version can be found 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);