reselect
Version:
Selectors for Redux.
494 lines (463 loc) • 20.2 kB
text/typescript
import { weakMapMemoize } from './weakMapMemoize'
import type {
Combiner,
CreateSelectorOptions,
DropFirstParameter,
ExtractMemoizerFields,
GetParamsFromSelectors,
GetStateFromSelectors,
InterruptRecursion,
OutputSelector,
Selector,
SelectorArray,
SetRequired,
Simplify,
UnknownMemoizer
} from './types'
import {
assertIsFunction,
collectInputSelectorResults,
ensureIsArray,
getDependencies,
getDevModeChecksExecutionInfo
} from './utils'
/**
* An instance of `createSelector`, customized with a given memoize implementation.
*
* @template MemoizeFunction - The type of the memoize function that is used to memoize the `resultFunc` inside `createSelector` (e.g., `lruMemoize` or `weakMapMemoize`).
* @template ArgsMemoizeFunction - The type of the optional memoize function that is used to memoize the arguments passed into the output selector generated by `createSelector` (e.g., `lruMemoize` or `weakMapMemoize`). If none is explicitly provided, `weakMapMemoize` will be used.
* @template StateType - The type of state that the selectors created with this selector creator will operate on.
*
* @public
*/
export interface CreateSelectorFunction<
MemoizeFunction extends UnknownMemoizer = typeof weakMapMemoize,
ArgsMemoizeFunction extends UnknownMemoizer = typeof weakMapMemoize,
StateType = any
> {
/**
* Creates a memoized selector function.
*
* @param createSelectorArgs - An arbitrary number of input selectors as separate inline arguments and a `combiner` function.
* @returns A memoized output selector.
*
* @template InputSelectors - The type of the input selectors as an array.
* @template Result - The return type of the `combiner` as well as the output selector.
* @template OverrideMemoizeFunction - The type of the optional `memoize` function that could be passed into the options object to override the original `memoize` function that was initially passed into `createSelectorCreator`.
* @template OverrideArgsMemoizeFunction - The type of the optional `argsMemoize` function that could be passed into the options object to override the original `argsMemoize` function that was initially passed into `createSelectorCreator`.
*
* @see {@link https://reselect.js.org/api/createselector `createSelector`}
*/
<InputSelectors extends SelectorArray<StateType>, Result>(
...createSelectorArgs: [
...inputSelectors: InputSelectors,
combiner: Combiner<InputSelectors, Result>
]
): OutputSelector<
InputSelectors,
Result,
MemoizeFunction,
ArgsMemoizeFunction
> &
InterruptRecursion
/**
* Creates a memoized selector function.
*
* @param createSelectorArgs - An arbitrary number of input selectors as separate inline arguments, a `combiner` function and an `options` object.
* @returns A memoized output selector.
*
* @template InputSelectors - The type of the input selectors as an array.
* @template Result - The return type of the `combiner` as well as the output selector.
* @template OverrideMemoizeFunction - The type of the optional `memoize` function that could be passed into the options object to override the original `memoize` function that was initially passed into `createSelectorCreator`.
* @template OverrideArgsMemoizeFunction - The type of the optional `argsMemoize` function that could be passed into the options object to override the original `argsMemoize` function that was initially passed into `createSelectorCreator`.
*
* @see {@link https://reselect.js.org/api/createselector `createSelector`}
*/
<
InputSelectors extends SelectorArray<StateType>,
Result,
OverrideMemoizeFunction extends UnknownMemoizer = MemoizeFunction,
OverrideArgsMemoizeFunction extends UnknownMemoizer = ArgsMemoizeFunction
>(
...createSelectorArgs: [
...inputSelectors: InputSelectors,
combiner: Combiner<InputSelectors, Result>,
createSelectorOptions: Simplify<
CreateSelectorOptions<
MemoizeFunction,
ArgsMemoizeFunction,
OverrideMemoizeFunction,
OverrideArgsMemoizeFunction
>
>
]
): OutputSelector<
InputSelectors,
Result,
OverrideMemoizeFunction,
OverrideArgsMemoizeFunction
> &
InterruptRecursion
/**
* Creates a memoized selector function.
*
* @param inputSelectors - An array of input selectors.
* @param combiner - A function that Combines the input selectors and returns an output selector. Otherwise known as the result function.
* @param createSelectorOptions - An optional options object that allows for further customization per selector.
* @returns A memoized output selector.
*
* @template InputSelectors - The type of the input selectors array.
* @template Result - The return type of the `combiner` as well as the output selector.
* @template OverrideMemoizeFunction - The type of the optional `memoize` function that could be passed into the options object to override the original `memoize` function that was initially passed into `createSelectorCreator`.
* @template OverrideArgsMemoizeFunction - The type of the optional `argsMemoize` function that could be passed into the options object to override the original `argsMemoize` function that was initially passed into `createSelectorCreator`.
*
* @see {@link https://reselect.js.org/api/createselector `createSelector`}
*/
<
InputSelectors extends SelectorArray<StateType>,
Result,
OverrideMemoizeFunction extends UnknownMemoizer = MemoizeFunction,
OverrideArgsMemoizeFunction extends UnknownMemoizer = ArgsMemoizeFunction
>(
inputSelectors: [...InputSelectors],
combiner: Combiner<InputSelectors, Result>,
createSelectorOptions?: Simplify<
CreateSelectorOptions<
MemoizeFunction,
ArgsMemoizeFunction,
OverrideMemoizeFunction,
OverrideArgsMemoizeFunction
>
>
): OutputSelector<
InputSelectors,
Result,
OverrideMemoizeFunction,
OverrideArgsMemoizeFunction
> &
InterruptRecursion
/**
* Creates a "pre-typed" version of {@linkcode createSelector createSelector}
* where the `state` type is predefined.
*
* This allows you to set the `state` type once, eliminating the need to
* specify it with every {@linkcode createSelector createSelector} call.
*
* @returns A pre-typed `createSelector` with the state type already defined.
*
* @example
* ```ts
* import { createSelector } from 'reselect'
*
* export interface RootState {
* todos: { id: number; completed: boolean }[]
* alerts: { id: number; read: boolean }[]
* }
*
* export const createAppSelector = createSelector.withTypes<RootState>()
*
* const selectTodoIds = createAppSelector(
* [
* // Type of `state` is set to `RootState`, no need to manually set the type
* state => state.todos
* ],
* todos => todos.map(({ id }) => id)
* )
* ```
* @template OverrideStateType - The specific type of state used by all selectors created with this selector creator.
*
* @see {@link https://reselect.js.org/api/createselector#defining-a-pre-typed-createselector `createSelector.withTypes`}
*
* @since 5.1.0
*/
withTypes: <OverrideStateType extends StateType>() => CreateSelectorFunction<
MemoizeFunction,
ArgsMemoizeFunction,
OverrideStateType
>
}
/**
* Creates a selector creator function with the specified memoization function
* and options for customizing memoization behavior.
*
* @param options - An options object containing the `memoize` function responsible for memoizing the `resultFunc` inside `createSelector` (e.g., `lruMemoize` or `weakMapMemoize`). It also provides additional options for customizing memoization. While the `memoize` property is mandatory, the rest are optional.
* @returns A customized `createSelector` function.
*
* @example
* ```ts
* const customCreateSelector = createSelectorCreator({
* memoize: customMemoize, // Function to be used to memoize `resultFunc`
* memoizeOptions: [memoizeOption1, memoizeOption2], // Options passed to `customMemoize` as the second argument onwards
* argsMemoize: customArgsMemoize, // Function to be used to memoize the selector's arguments
* argsMemoizeOptions: [argsMemoizeOption1, argsMemoizeOption2] // Options passed to `customArgsMemoize` as the second argument onwards
* })
*
* const customSelector = customCreateSelector(
* [inputSelector1, inputSelector2],
* resultFunc // `resultFunc` will be passed as the first argument to `customMemoize`
* )
*
* customSelector(
* ...selectorArgs // Will be memoized by `customArgsMemoize`
* )
* ```
*
* @template MemoizeFunction - The type of the memoize function that is used to memoize the `resultFunc` inside `createSelector` (e.g., `lruMemoize` or `weakMapMemoize`).
* @template ArgsMemoizeFunction - The type of the optional memoize function that is used to memoize the arguments passed into the output selector generated by `createSelector` (e.g., `lruMemoize` or `weakMapMemoize`). If none is explicitly provided, `weakMapMemoize` will be used.
*
* @see {@link https://reselect.js.org/api/createSelectorCreator#using-options-since-500 `createSelectorCreator`}
*
* @since 5.0.0
* @public
*/
export function createSelectorCreator<
MemoizeFunction extends UnknownMemoizer,
ArgsMemoizeFunction extends UnknownMemoizer = typeof weakMapMemoize
>(
options: Simplify<
SetRequired<
CreateSelectorOptions<
typeof weakMapMemoize,
typeof weakMapMemoize,
MemoizeFunction,
ArgsMemoizeFunction
>,
'memoize'
>
>
): CreateSelectorFunction<MemoizeFunction, ArgsMemoizeFunction>
/**
* Creates a selector creator function with the specified memoization function
* and options for customizing memoization behavior.
*
* @param memoize - The `memoize` function responsible for memoizing the `resultFunc` inside `createSelector` (e.g., `lruMemoize` or `weakMapMemoize`).
* @param memoizeOptionsFromArgs - Optional configuration options for the memoization function. These options are then passed to the memoize function as the second argument onwards.
* @returns A customized `createSelector` function.
*
* @example
* ```ts
* const customCreateSelector = createSelectorCreator(customMemoize, // Function to be used to memoize `resultFunc`
* option1, // Will be passed as second argument to `customMemoize`
* option2, // Will be passed as third argument to `customMemoize`
* option3 // Will be passed as fourth argument to `customMemoize`
* )
*
* const customSelector = customCreateSelector(
* [inputSelector1, inputSelector2],
* resultFunc // `resultFunc` will be passed as the first argument to `customMemoize`
* )
* ```
*
* @template MemoizeFunction - The type of the memoize function that is used to memoize the `resultFunc` inside `createSelector` (e.g., `lruMemoize` or `weakMapMemoize`).
*
* @see {@link https://reselect.js.org/api/createSelectorCreator#using-memoize-and-memoizeoptions `createSelectorCreator`}
*
* @public
*/
export function createSelectorCreator<MemoizeFunction extends UnknownMemoizer>(
memoize: MemoizeFunction,
...memoizeOptionsFromArgs: DropFirstParameter<MemoizeFunction>
): CreateSelectorFunction<MemoizeFunction>
/**
* Creates a selector creator function with the specified memoization
* function and options for customizing memoization behavior.
*
* @param memoizeOrOptions - Either A `memoize` function or an `options` object containing the `memoize` function.
* @param memoizeOptionsFromArgs - Optional configuration options for the memoization function. These options are then passed to the memoize function as the second argument onwards.
* @returns A customized `createSelector` function.
*
* @template MemoizeFunction - The type of the memoize function that is used to memoize the `resultFunc` inside `createSelector` (e.g., `lruMemoize` or `weakMapMemoize`).
* @template ArgsMemoizeFunction - The type of the optional memoize function that is used to memoize the arguments passed into the output selector generated by `createSelector` (e.g., `lruMemoize` or `weakMapMemoize`). If none is explicitly provided, `weakMapMemoize` will be used.
* @template MemoizeOrOptions - The type of the first argument. It can either be a `memoize` function or an `options` object containing the `memoize` function.
*/
export function createSelectorCreator<
MemoizeFunction extends UnknownMemoizer,
ArgsMemoizeFunction extends UnknownMemoizer,
MemoizeOrOptions extends
| MemoizeFunction
| SetRequired<
CreateSelectorOptions<MemoizeFunction, ArgsMemoizeFunction>,
'memoize'
>
>(
memoizeOrOptions: MemoizeOrOptions,
...memoizeOptionsFromArgs: MemoizeOrOptions extends SetRequired<
CreateSelectorOptions<MemoizeFunction, ArgsMemoizeFunction>,
'memoize'
>
? never
: DropFirstParameter<MemoizeFunction>
) {
/** options initially passed into `createSelectorCreator`. */
const createSelectorCreatorOptions: SetRequired<
CreateSelectorOptions<MemoizeFunction, ArgsMemoizeFunction>,
'memoize'
> = typeof memoizeOrOptions === 'function'
? {
memoize: memoizeOrOptions as MemoizeFunction,
memoizeOptions: memoizeOptionsFromArgs
}
: memoizeOrOptions
const createSelector = <
InputSelectors extends SelectorArray,
Result,
OverrideMemoizeFunction extends UnknownMemoizer = MemoizeFunction,
OverrideArgsMemoizeFunction extends UnknownMemoizer = ArgsMemoizeFunction
>(
...createSelectorArgs: [
...inputSelectors: [...InputSelectors],
combiner: Combiner<InputSelectors, Result>,
createSelectorOptions?: CreateSelectorOptions<
MemoizeFunction,
ArgsMemoizeFunction,
OverrideMemoizeFunction,
OverrideArgsMemoizeFunction
>
]
) => {
let recomputations = 0
let dependencyRecomputations = 0
let lastResult: Result
// Due to the intricacies of rest params, we can't do an optional arg after `...createSelectorArgs`.
// So, start by declaring the default value here.
// (And yes, the words 'memoize' and 'options' appear too many times in this next sequence.)
let directlyPassedOptions: CreateSelectorOptions<
MemoizeFunction,
ArgsMemoizeFunction,
OverrideMemoizeFunction,
OverrideArgsMemoizeFunction
> = {}
// Normally, the result func or "combiner" is the last arg
let resultFunc = createSelectorArgs.pop() as
| Combiner<InputSelectors, Result>
| CreateSelectorOptions<
MemoizeFunction,
ArgsMemoizeFunction,
OverrideMemoizeFunction,
OverrideArgsMemoizeFunction
>
// If the result func is actually an _object_, assume it's our options object
if (typeof resultFunc === 'object') {
directlyPassedOptions = resultFunc
// and pop the real result func off
resultFunc = createSelectorArgs.pop() as Combiner<InputSelectors, Result>
}
assertIsFunction(
resultFunc,
`createSelector expects an output function after the inputs, but received: [${typeof resultFunc}]`
)
// Determine which set of options we're using. Prefer options passed directly,
// but fall back to options given to `createSelectorCreator`.
const combinedOptions = {
...createSelectorCreatorOptions,
...directlyPassedOptions
}
const {
memoize,
memoizeOptions = [],
argsMemoize = weakMapMemoize,
argsMemoizeOptions = [],
devModeChecks = {}
} = combinedOptions
// Simplifying assumption: it's unlikely that the first options arg of the provided memoizer
// is an array. In most libs I've looked at, it's an equality function or options object.
// Based on that, if `memoizeOptions` _is_ an array, we assume it's a full
// user-provided array of options. Otherwise, it must be just the _first_ arg, and so
// we wrap it in an array so we can apply it.
const finalMemoizeOptions = ensureIsArray(memoizeOptions)
const finalArgsMemoizeOptions = ensureIsArray(argsMemoizeOptions)
const dependencies = getDependencies(createSelectorArgs) as InputSelectors
const memoizedResultFunc = memoize(function recomputationWrapper() {
recomputations++
// apply arguments instead of spreading for performance.
// @ts-ignore
return (resultFunc as Combiner<InputSelectors, Result>).apply(
null,
arguments as unknown as Parameters<Combiner<InputSelectors, Result>>
)
}, ...finalMemoizeOptions) as Combiner<InputSelectors, Result> &
ExtractMemoizerFields<OverrideMemoizeFunction>
let firstRun = true
// If a selector is called with the exact same arguments we don't need to traverse our dependencies again.
const selector = argsMemoize(function dependenciesChecker() {
dependencyRecomputations++
/** Return values of input selectors which the `resultFunc` takes as arguments. */
const inputSelectorResults = collectInputSelectorResults(
dependencies,
arguments
)
// apply arguments instead of spreading for performance.
// @ts-ignore
lastResult = memoizedResultFunc.apply(null, inputSelectorResults)
if (process.env.NODE_ENV !== 'production') {
const { identityFunctionCheck, inputStabilityCheck } =
getDevModeChecksExecutionInfo(firstRun, devModeChecks)
if (identityFunctionCheck.shouldRun) {
identityFunctionCheck.run(
resultFunc as Combiner<InputSelectors, Result>,
inputSelectorResults,
lastResult
)
}
if (inputStabilityCheck.shouldRun) {
// make a second copy of the params, to check if we got the same results
const inputSelectorResultsCopy = collectInputSelectorResults(
dependencies,
arguments
)
inputStabilityCheck.run(
{ inputSelectorResults, inputSelectorResultsCopy },
{ memoize, memoizeOptions: finalMemoizeOptions },
arguments
)
}
if (firstRun) firstRun = false
}
return lastResult
}, ...finalArgsMemoizeOptions) as unknown as Selector<
GetStateFromSelectors<InputSelectors>,
Result,
GetParamsFromSelectors<InputSelectors>
> &
ExtractMemoizerFields<OverrideArgsMemoizeFunction>
return Object.assign(selector, {
resultFunc,
memoizedResultFunc,
dependencies,
dependencyRecomputations: () => dependencyRecomputations,
resetDependencyRecomputations: () => {
dependencyRecomputations = 0
},
lastResult: () => lastResult,
recomputations: () => recomputations,
resetRecomputations: () => {
recomputations = 0
},
memoize,
argsMemoize
}) as OutputSelector<
InputSelectors,
Result,
OverrideMemoizeFunction,
OverrideArgsMemoizeFunction
>
}
Object.assign(createSelector, {
withTypes: () => createSelector
})
return createSelector as CreateSelectorFunction<
MemoizeFunction,
ArgsMemoizeFunction
>
}
/**
* Accepts one or more "input selectors" (either as separate arguments or a single array),
* a single "result function" / "combiner", and an optional options object, and
* generates a memoized selector function.
*
* @see {@link https://reselect.js.org/api/createSelector `createSelector`}
*
* @public
*/
export const createSelector =
/* #__PURE__ */ createSelectorCreator(weakMapMemoize)