我从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 },
]
}
});
答案 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);