-
Notifications
You must be signed in to change notification settings - Fork 666
Update createSelector and defaultMemoize to accept options (maxSize, equalityCheck, resultEqualityCheck)
#513
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
714c0c4
Add initial options object type to createSelector args
markerikson 21865e4
Infer all memoizer arguments directly, without overloads
markerikson e2eb3f6
Add memoize-one and micro-memoize test deps
markerikson b715d9d
Add additional custom memoizer checks
markerikson 29cdecc
Implement passing memoize options directly to `createSelector`
markerikson d5711c9
Group selector tests with meaningful descriptions
markerikson 8dead22
Update defaultMemoize to accept an options object as its arg
markerikson 98013a1
Extract defaultMemoize to a separate file
markerikson 7284ba3
Rewrite defaultMemoize to use an LRU cache with configurable size
markerikson c1d3f98
Add support for reusing a recent value if resultEqualityCheck given
markerikson 3275e1e
Update build inputs and remove standalone typedefs
markerikson ee1500d
Rename field to `memoizeOptions`
markerikson File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,153 @@ | ||
| import type { EqualityFn } from './types' | ||
|
|
||
| // Cache implementation based on Erik Rasmussen's `lru-memoize`: | ||
| // https://github.com/erikras/lru-memoize | ||
|
|
||
| interface Entry { | ||
| key: unknown | ||
| value: unknown | ||
| } | ||
|
|
||
| interface Cache { | ||
| get(key: unknown): unknown | undefined | ||
| put(key: unknown, value: unknown): void | ||
| getValues(): unknown[] | ||
| } | ||
|
|
||
| function createSingletonCache(equals: EqualityFn): Cache { | ||
| let entry: Entry | ||
| return { | ||
| get(key: unknown) { | ||
| if (entry && equals(entry.key, key)) { | ||
| return entry.value | ||
| } | ||
| }, | ||
|
|
||
| put(key: unknown, value: unknown) { | ||
| entry = { key, value } | ||
| }, | ||
|
|
||
| getValues() { | ||
| return entry ? [entry.value] : [] | ||
| } | ||
| } | ||
| } | ||
|
|
||
| function createLruCache(maxSize: number, equals: EqualityFn): Cache { | ||
| const entries: Entry[] = [] | ||
|
|
||
| function get(key: unknown) { | ||
| const cacheIndex = entries.findIndex(entry => equals(key, entry.key)) | ||
|
|
||
| // We found a cached entry | ||
| if (cacheIndex > -1) { | ||
| const entry = entries[cacheIndex] | ||
|
|
||
| // Cached entry not at top of cache, move it to the top | ||
| if (cacheIndex > 0) { | ||
| entries.splice(cacheIndex, 1) | ||
| entries.unshift(entry) | ||
| } | ||
|
|
||
| return entry.value | ||
| } | ||
|
|
||
| // No entry found in cache, return null | ||
| return undefined | ||
| } | ||
|
|
||
| function put(key: unknown, value: unknown) { | ||
| if (!get(key)) { | ||
| // TODO Is unshift slow? | ||
| entries.unshift({ key, value }) | ||
| if (entries.length > maxSize) { | ||
| entries.pop() | ||
| } | ||
| } | ||
| } | ||
|
|
||
| function getValues() { | ||
| return entries.map(entry => entry.value) | ||
| } | ||
|
|
||
| return { get, put, getValues } | ||
| } | ||
|
|
||
| export const defaultEqualityCheck: EqualityFn = (a, b): boolean => { | ||
| return a === b | ||
| } | ||
|
|
||
| export function createCacheKeyComparator(equalityCheck: EqualityFn) { | ||
| return function areArgumentsShallowlyEqual( | ||
| prev: unknown[] | IArguments | null, | ||
| next: unknown[] | IArguments | null | ||
| ): boolean { | ||
| 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. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Noice! |
||
| const length = prev.length | ||
| for (let i = 0; i < length; i++) { | ||
| if (!equalityCheck(prev[i], next[i])) { | ||
| return false | ||
| } | ||
| } | ||
|
|
||
| return true | ||
| } | ||
| } | ||
|
|
||
| export interface DefaultMemoizeOptions { | ||
| equalityCheck?: EqualityFn | ||
| resultEqualityCheck?: EqualityFn | ||
| maxSize?: number | ||
| } | ||
|
|
||
| // defaultMemoize now supports a configurable cache size with LRU behavior, | ||
| // and optional comparison of the result value with existing values | ||
| export function defaultMemoize<F extends (...args: any[]) => any>( | ||
| func: F, | ||
| equalityCheckOrOptions?: EqualityFn | DefaultMemoizeOptions | ||
| ): F { | ||
| const providedOptions = | ||
| typeof equalityCheckOrOptions === 'object' | ||
| ? equalityCheckOrOptions | ||
| : { equalityCheck: equalityCheckOrOptions } | ||
|
|
||
| const { | ||
| equalityCheck = defaultEqualityCheck, | ||
| maxSize = 1, | ||
| resultEqualityCheck | ||
| } = providedOptions | ||
|
|
||
| const comparator = createCacheKeyComparator(equalityCheck) | ||
|
|
||
| const cache = | ||
| maxSize === 1 | ||
| ? createSingletonCache(comparator) | ||
| : createLruCache(maxSize, comparator) | ||
|
|
||
| // we reference arguments instead of spreading them for performance reasons | ||
| return function () { | ||
| let value = cache.get(arguments) | ||
| if (value === undefined) { | ||
| // @ts-ignore | ||
| value = func.apply(null, arguments) | ||
|
|
||
| if (resultEqualityCheck) { | ||
| const existingValues = cache.getValues() | ||
| const matchingValue = existingValues.find(ev => | ||
| resultEqualityCheck(ev, value) | ||
| ) | ||
|
|
||
| if (matchingValue !== undefined) { | ||
| return matchingValue | ||
| } | ||
| } | ||
|
|
||
| cache.put(arguments, value) | ||
| } | ||
| return value | ||
| } as F | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
6.0.0has just been released 🎉