Skip to content

Commit

Permalink
Merge pull request #633 from aryaemami59/hover-preview
Browse files Browse the repository at this point in the history
  • Loading branch information
markerikson committed Nov 1, 2023
2 parents 6a009b6 + 2614993 commit 9966bf2
Show file tree
Hide file tree
Showing 21 changed files with 2,061 additions and 631 deletions.
15 changes: 8 additions & 7 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"env": {
"browser": true,
"node": true,
"node": true
},
"extends": "eslint:recommended",
"parserOptions": {
Expand All @@ -14,7 +14,11 @@
"eol-last": 2,
"no-multiple-empty-lines": 2,
"object-curly-spacing": [2, "always"],
"quotes": [2, "single", {"avoidEscape": true, "allowTemplateLiterals": true}],
"quotes": [
2,
"single",
{ "avoidEscape": true, "allowTemplateLiterals": true }
],
"semi": [2, "never"],
"strict": 0,
"space-before-blocks": [2, "always"],
Expand Down Expand Up @@ -45,8 +49,8 @@
"@typescript-eslint/no-use-before-define": ["off"],
"@typescript-eslint/ban-types": "off",
"prefer-rest-params": "off",
"prefer-rest": "off",
"prefer-spread": "off"
"prefer-spread": "off",
"@typescript-eslint/consistent-type-imports": [2]
}
},
{
Expand All @@ -61,9 +65,6 @@
"@typescript-eslint/no-floating-promises": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/camelcase": "off",
"import/max-dependencies": "off",
"sonarjs/no-duplicate-string": "off",
"@typescript-eslint/no-shadow": "off"
}
}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"format": "prettier --write \"{src,test}/**/*.{js,ts}\" \"docs/**/*.md\"",
"lint": "eslint src test",
"prepack": "yarn build",
"bench": "vitest --run bench",
"test": "node --expose-gc ./node_modules/vitest/dist/cli-wrapper.js run",
"test:cov": "vitest run --coverage",
"test:typescript": "tsc --noEmit -p typescript_test/tsconfig.json"
Expand Down
65 changes: 63 additions & 2 deletions src/autotrackMemoize/autotrackMemoize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,68 @@ import {
import type { AnyFunction } from '@internal/types'
import { createCache } from './autotracking'

export function autotrackMemoize<F extends AnyFunction>(func: F) {
/**
* Uses an "auto-tracking" approach inspired by the work of the Ember Glimmer team.
* It uses a Proxy to wrap arguments and track accesses to nested fields
* in your selector on first read. Later, when the selector is called with
* new arguments, it identifies which accessed fields have changed and
* only recalculates the result if one or more of those accessed fields have changed.
* This allows it to be more precise than the shallow equality checks in `defaultMemoize`.
*
* __Design Tradeoffs for `autotrackMemoize`:__
* - Pros:
* - It is likely to avoid excess calculations and recalculate fewer times than `defaultMemoize` will,
* which may also result in fewer component re-renders.
* - Cons:
* - It only has a cache size of 1.
* - It is slower than `defaultMemoize`, because it has to do more work. (How much slower is dependent on the number of accessed fields in a selector, number of calls, frequency of input changes, etc)
* - It can have some unexpected behavior. Because it tracks nested field accesses,
* cases where you don't access a field will not recalculate properly.
* For example, a badly-written selector like:
* ```ts
* createSelector([state => state.todos], todos => todos)
* ```
* that just immediately returns the extracted value will never update, because it doesn't see any field accesses to check.
*
* __Use Cases for `autotrackMemoize`:__
* - It is likely best used for cases where you need to access specific nested fields
* in data, and avoid recalculating if other fields in the same data objects are immutably updated.
*
* @param func - The function to be memoized.
* @returns A memoized function with a `.clearCache()` method attached.
*
* @example
* <caption>Using `createSelector`</caption>
* ```ts
* import { unstable_autotrackMemoize as autotrackMemoize, createSelector } from 'reselect'
*
* const selectTodoIds = createSelector(
* [(state: RootState) => state.todos],
* (todos) => todos.map(todo => todo.id),
* { memoize: autotrackMemoize }
* )
* ```
*
* @example
* <caption>Using `createSelectorCreator`</caption>
* ```ts
* import { unstable_autotrackMemoize as autotrackMemoize, createSelectorCreator } from 'reselect'
*
* const createSelectorAutotrack = createSelectorCreator(autotrackMemoize)
*
* const selectTodoIds = createSelectorAutotrack(
* [(state: RootState) => state.todos],
* (todos) => todos.map(todo => todo.id)
* )
* ```
*
* @template Func - The type of the function that is memoized.
*
* @since 5.0.0
* @public
* @experimental
*/
export function autotrackMemoize<Func extends AnyFunction>(func: Func) {
// we reference arguments instead of spreading them for performance reasons

const node: Node<Record<string, unknown>> = createNode(
Expand All @@ -34,5 +95,5 @@ export function autotrackMemoize<F extends AnyFunction>(func: F) {

memoized.clearCache = () => cache.clear()

return memoized as F & { clearCache: () => void }
return memoized as Func & { clearCache: () => void }
}
15 changes: 8 additions & 7 deletions src/autotrackMemoize/autotracking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// - https://www.pzuraq.com/blog/how-autotracking-works
// - https://v5.chriskrycho.com/journal/autotracking-elegant-dx-via-cutting-edge-cs/
import type { EqualityFn } from '@internal/types'
import { assert } from './utils'
import { assertIsFunction } from '@internal/utils'

// The global revision clock. Every time state changes, the clock increments.
export let $REVISION = 0
Expand Down Expand Up @@ -133,10 +133,11 @@ export function setValue<T extends Cell<unknown>>(
storage: T,
value: CellValue<T>
): void {
assert(
storage instanceof Cell,
'setValue must be passed a tracked store created with `createStorage`.'
)
if (!(storage instanceof Cell)) {
throw new TypeError(
'setValue must be passed a tracked store created with `createStorage`.'
)
}

storage.value = storage._lastValue = value
}
Expand All @@ -149,8 +150,8 @@ export function createCell<T = unknown>(
}

export function createCache<T = unknown>(fn: () => T): TrackingCache {
assert(
typeof fn === 'function',
assertIsFunction(
fn,
'the first parameter to `createCache` must be a function'
)

Expand Down
76 changes: 50 additions & 26 deletions src/createSelectorCreator.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { OutputSelector, Selector, SelectorArray } from 'reselect'
import { defaultMemoize } from './defaultMemoize'

import type {
Expand All @@ -7,9 +8,7 @@ import type {
ExtractMemoizerFields,
GetParamsFromSelectors,
GetStateFromSelectors,
OutputSelector,
Selector,
SelectorArray,
InterruptRecursion,
StabilityCheckFrequency,
UnknownMemoizer
} from './types'
Expand All @@ -28,21 +27,25 @@ import {
*
* @template MemoizeFunction - The type of the memoize function that is used to memoize the `resultFunc` inside `createSelector` (e.g., `defaultMemoize` 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., `defaultMemoize` or `weakMapMemoize`). If none is explicitly provided, `defaultMemoize` will be used.
*
* @public
*/
export interface CreateSelectorFunction<
MemoizeFunction extends UnknownMemoizer,
MemoizeFunction extends UnknownMemoizer = typeof defaultMemoize,
ArgsMemoizeFunction extends UnknownMemoizer = typeof defaultMemoize
> {
/**
* Creates a memoized selector function.
*
* @param createSelectorArgs - An arbitrary number of input selectors as separate inline arguments and a `combiner` function.
* @returns An output selector.
* @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://github.com/reduxjs/reselect#createselectorinputselectors--inputselectors-resultfunc-selectoroptions createSelector}
*/
<InputSelectors extends SelectorArray, Result>(
...createSelectorArgs: [
Expand All @@ -54,18 +57,21 @@ export interface CreateSelectorFunction<
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 An output selector.
* @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://github.com/reduxjs/reselect#createselectorinputselectors--inputselectors-resultfunc-selectoroptions createSelector}
*/
<
InputSelectors extends SelectorArray,
Expand All @@ -90,20 +96,23 @@ export interface CreateSelectorFunction<
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 An output 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://github.com/reduxjs/reselect#createselectorinputselectors--inputselectors-resultfunc-selectoroptions createSelector}
*/
<
InputSelectors extends SelectorArray,
Expand All @@ -126,7 +135,8 @@ export interface CreateSelectorFunction<
Result,
OverrideMemoizeFunction,
OverrideArgsMemoizeFunction
>
> &
InterruptRecursion
}

let globalStabilityCheck: StabilityCheckFrequency = 'once'
Expand All @@ -149,6 +159,8 @@ let globalStabilityCheck: StabilityCheckFrequency = 'once'
* @example
* ```ts
* import { setInputStabilityCheckEnabled } from 'reselect'
import { assert } from './autotrackMemoize/utils';
import { OutputSelectorFields, Mapped } from './types';
*
* // Run only the first time the selector is called. (default)
* setInputStabilityCheckEnabled('once')
Expand All @@ -161,6 +173,9 @@ let globalStabilityCheck: StabilityCheckFrequency = 'once'
* ```
* @see {@link https://github.com/reduxjs/reselect#development-only-checks development-only-checks}
* @see {@link https://github.com/reduxjs/reselect#global-configuration global-configuration}
*
* @since 5.0.0
* @public
*/
export function setInputStabilityCheckEnabled(
inputStabilityCheckFrequency: StabilityCheckFrequency
Expand Down Expand Up @@ -195,15 +210,20 @@ export function setInputStabilityCheckEnabled(
*
* @template MemoizeFunction - The type of the memoize function that is used to memoize the `resultFunc` inside `createSelector` (e.g., `defaultMemoize` 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., `defaultMemoize` or `weakMapMemoize`). If none is explicitly provided, `defaultMemoize` will be used.
*
* @see {@link https://github.com/reduxjs/reselect#createselectorcreatormemoize-memoizeoptions createSelectorCreator}
*
* @since 5.0.0
* @public
*/
export function createSelectorCreator<
MemoizeFunction extends UnknownMemoizer,
ArgsMemoizeFunction extends UnknownMemoizer = typeof defaultMemoize
>(
options: CreateSelectorOptions<
MemoizeFunction,
typeof defaultMemoize,
never,
typeof defaultMemoize,
MemoizeFunction,
ArgsMemoizeFunction
>
): CreateSelectorFunction<MemoizeFunction, ArgsMemoizeFunction>
Expand All @@ -230,6 +250,10 @@ export function createSelectorCreator<
* ```
*
* @template MemoizeFunction - The type of the memoize function that is used to memoize the `resultFunc` inside `createSelector` (e.g., `defaultMemoize` or `weakMapMemoize`).
*
* @see {@link https://github.com/reduxjs/reselect#createselectorcreatormemoize-memoizeoptions createSelectorCreator}
*
* @public
*/
export function createSelectorCreator<MemoizeFunction extends UnknownMemoizer>(
memoize: MemoizeFunction,
Expand Down Expand Up @@ -280,7 +304,7 @@ export function createSelectorCreator<
OverrideMemoizeFunction extends UnknownMemoizer = MemoizeFunction,
OverrideArgsMemoizeFunction extends UnknownMemoizer = ArgsMemoizeFunction
>(
...funcs: [
...createSelectorArgs: [
...inputSelectors: [...InputSelectors],
combiner: Combiner<InputSelectors, Result>,
createSelectorOptions?: Partial<
Expand Down Expand Up @@ -309,7 +333,7 @@ export function createSelectorCreator<
> = {}

// Normally, the result func or "combiner" is the last arg
let resultFunc = funcs.pop() as
let resultFunc = createSelectorArgs.pop() as
| Combiner<InputSelectors, Result>
| Partial<
CreateSelectorOptions<
Expand All @@ -324,7 +348,7 @@ export function createSelectorCreator<
if (typeof resultFunc === 'object') {
directlyPassedOptions = resultFunc
// and pop the real result func off
resultFunc = funcs.pop() as Combiner<InputSelectors, Result>
resultFunc = createSelectorArgs.pop() as Combiner<InputSelectors, Result>
}

assertIsFunction(
Expand Down Expand Up @@ -354,7 +378,7 @@ export function createSelectorCreator<
// we wrap it in an array so we can apply it.
const finalMemoizeOptions = ensureIsArray(memoizeOptions)
const finalArgsMemoizeOptions = ensureIsArray(argsMemoizeOptions)
const dependencies = getDependencies(funcs) as InputSelectors
const dependencies = getDependencies(createSelectorArgs) as InputSelectors

const memoizedResultFunc = memoize(function recomputationWrapper() {
recomputations++
Expand All @@ -370,15 +394,6 @@ export function createSelectorCreator<
let firstRun = true

// If a selector is called with the exact same arguments we don't need to traverse our dependencies again.
// TODO This was changed to `memoize` in 4.0.0 ( #297 ), but I changed it back.
// The original intent was to allow customizing things like skortchmark's
// selector debugging setup.
// But, there's multiple issues:
// - We don't pass in `memoizeOptions`
// Arguments change all the time, but input values change less often.
// Most of the time shallow equality _is_ what we really want here.
// TODO Rethink this change, or find a way to expose more options?
// @ts-ignore
const selector = argsMemoize(function dependenciesChecker() {
/** Return values of input selectors which the `resultFunc` takes as arguments. */
const inputSelectorResults = collectInputSelectorResults(
Expand Down Expand Up @@ -410,7 +425,7 @@ export function createSelectorCreator<
lastResult = memoizedResultFunc.apply(null, inputSelectorResults)

return lastResult
}, ...finalArgsMemoizeOptions) as Selector<
}, ...finalArgsMemoizeOptions) as unknown as Selector<
GetStateFromSelectors<InputSelectors>,
Result,
GetParamsFromSelectors<InputSelectors>
Expand Down Expand Up @@ -439,5 +454,14 @@ export function createSelectorCreator<
>
}

/**
* 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://github.com/reduxjs/reselect#createselectorinputselectors--inputselectors-resultfunc-selectoroptions createSelector}
*
* @public
*/
export const createSelector =
/* #__PURE__ */ createSelectorCreator(defaultMemoize)
Loading

0 comments on commit 9966bf2

Please sign in to comment.