UNPKG

reselect

Version:
494 lines (463 loc) 20.2 kB
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)