source: node_modules/reselect/src/weakMapMemoize.ts@ d24f17c

main
Last change on this file since d24f17c was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

  • Property mode set to 100644
File size: 7.5 KB
Line 
1// Original source:
2// - https://github.com/facebook/react/blob/0b974418c9a56f6c560298560265dcf4b65784bc/packages/react/src/ReactCache.js
3
4import type {
5 AnyFunction,
6 DefaultMemoizeFields,
7 EqualityFn,
8 Simplify
9} from './types'
10
11class StrongRef<T> {
12 constructor(private value: T) {}
13 deref() {
14 return this.value
15 }
16}
17
18const Ref =
19 typeof WeakRef !== 'undefined'
20 ? WeakRef
21 : (StrongRef as unknown as typeof WeakRef)
22
23const UNTERMINATED = 0
24const TERMINATED = 1
25
26interface UnterminatedCacheNode<T> {
27 /**
28 * Status, represents whether the cached computation returned a value or threw an error.
29 */
30 s: 0
31 /**
32 * Value, either the cached result or an error, depending on status.
33 */
34 v: void
35 /**
36 * Object cache, a `WeakMap` where non-primitive arguments are stored.
37 */
38 o: null | WeakMap<Function | Object, CacheNode<T>>
39 /**
40 * Primitive cache, a regular Map where primitive arguments are stored.
41 */
42 p: null | Map<string | number | null | void | symbol | boolean, CacheNode<T>>
43}
44
45interface TerminatedCacheNode<T> {
46 /**
47 * Status, represents whether the cached computation returned a value or threw an error.
48 */
49 s: 1
50 /**
51 * Value, either the cached result or an error, depending on status.
52 */
53 v: T
54 /**
55 * Object cache, a `WeakMap` where non-primitive arguments are stored.
56 */
57 o: null | WeakMap<Function | Object, CacheNode<T>>
58 /**
59 * Primitive cache, a regular `Map` where primitive arguments are stored.
60 */
61 p: null | Map<string | number | null | void | symbol | boolean, CacheNode<T>>
62}
63
64type CacheNode<T> = TerminatedCacheNode<T> | UnterminatedCacheNode<T>
65
66function createCacheNode<T>(): CacheNode<T> {
67 return {
68 s: UNTERMINATED,
69 v: undefined,
70 o: null,
71 p: null
72 }
73}
74
75/**
76 * Configuration options for a memoization function utilizing `WeakMap` for
77 * its caching mechanism.
78 *
79 * @template Result - The type of the return value of the memoized function.
80 *
81 * @since 5.0.0
82 * @public
83 */
84export interface WeakMapMemoizeOptions<Result = any> {
85 /**
86 * If provided, used to compare a newly generated output value against previous values in the cache.
87 * If a match is found, the old value is returned. This addresses the common
88 * ```ts
89 * todos.map(todo => todo.id)
90 * ```
91 * use case, where an update to another field in the original data causes a recalculation
92 * due to changed references, but the output is still effectively the same.
93 *
94 * @since 5.0.0
95 */
96 resultEqualityCheck?: EqualityFn<Result>
97}
98
99/**
100 * Creates a tree of `WeakMap`-based cache nodes based on the identity of the
101 * arguments it's been called with (in this case, the extracted values from your input selectors).
102 * This allows `weakMapMemoize` to have an effectively infinite cache size.
103 * Cache results will be kept in memory as long as references to the arguments still exist,
104 * and then cleared out as the arguments are garbage-collected.
105 *
106 * __Design Tradeoffs for `weakMapMemoize`:__
107 * - Pros:
108 * - It has an effectively infinite cache size, but you have no control over
109 * how long values are kept in cache as it's based on garbage collection and `WeakMap`s.
110 * - Cons:
111 * - There's currently no way to alter the argument comparisons.
112 * They're based on strict reference equality.
113 * - It's roughly the same speed as `lruMemoize`, although likely a fraction slower.
114 *
115 * __Use Cases for `weakMapMemoize`:__
116 * - This memoizer is likely best used for cases where you need to call the
117 * same selector instance with many different arguments, such as a single
118 * selector instance that is used in a list item component and called with
119 * item IDs like:
120 * ```ts
121 * useSelector(state => selectSomeData(state, props.category))
122 * ```
123 * @param func - The function to be memoized.
124 * @returns A memoized function with a `.clearCache()` method attached.
125 *
126 * @example
127 * <caption>Using `createSelector`</caption>
128 * ```ts
129 * import { createSelector, weakMapMemoize } from 'reselect'
130 *
131 * interface RootState {
132 * items: { id: number; category: string; name: string }[]
133 * }
134 *
135 * const selectItemsByCategory = createSelector(
136 * [
137 * (state: RootState) => state.items,
138 * (state: RootState, category: string) => category
139 * ],
140 * (items, category) => items.filter(item => item.category === category),
141 * {
142 * memoize: weakMapMemoize,
143 * argsMemoize: weakMapMemoize
144 * }
145 * )
146 * ```
147 *
148 * @example
149 * <caption>Using `createSelectorCreator`</caption>
150 * ```ts
151 * import { createSelectorCreator, weakMapMemoize } from 'reselect'
152 *
153 * const createSelectorWeakMap = createSelectorCreator({ memoize: weakMapMemoize, argsMemoize: weakMapMemoize })
154 *
155 * const selectItemsByCategory = createSelectorWeakMap(
156 * [
157 * (state: RootState) => state.items,
158 * (state: RootState, category: string) => category
159 * ],
160 * (items, category) => items.filter(item => item.category === category)
161 * )
162 * ```
163 *
164 * @template Func - The type of the function that is memoized.
165 *
166 * @see {@link https://reselect.js.org/api/weakMapMemoize `weakMapMemoize`}
167 *
168 * @since 5.0.0
169 * @public
170 * @experimental
171 */
172export function weakMapMemoize<Func extends AnyFunction>(
173 func: Func,
174 options: WeakMapMemoizeOptions<ReturnType<Func>> = {}
175) {
176 let fnNode = createCacheNode()
177 const { resultEqualityCheck } = options
178
179 let lastResult: WeakRef<object> | undefined
180
181 let resultsCount = 0
182
183 function memoized() {
184 let cacheNode = fnNode
185 const { length } = arguments
186 for (let i = 0, l = length; i < l; i++) {
187 const arg = arguments[i]
188 if (
189 typeof arg === 'function' ||
190 (typeof arg === 'object' && arg !== null)
191 ) {
192 // Objects go into a WeakMap
193 let objectCache = cacheNode.o
194 if (objectCache === null) {
195 cacheNode.o = objectCache = new WeakMap()
196 }
197 const objectNode = objectCache.get(arg)
198 if (objectNode === undefined) {
199 cacheNode = createCacheNode()
200 objectCache.set(arg, cacheNode)
201 } else {
202 cacheNode = objectNode
203 }
204 } else {
205 // Primitives go into a regular Map
206 let primitiveCache = cacheNode.p
207 if (primitiveCache === null) {
208 cacheNode.p = primitiveCache = new Map()
209 }
210 const primitiveNode = primitiveCache.get(arg)
211 if (primitiveNode === undefined) {
212 cacheNode = createCacheNode()
213 primitiveCache.set(arg, cacheNode)
214 } else {
215 cacheNode = primitiveNode
216 }
217 }
218 }
219
220 const terminatedNode = cacheNode as unknown as TerminatedCacheNode<any>
221
222 let result
223
224 if (cacheNode.s === TERMINATED) {
225 result = cacheNode.v
226 } else {
227 // Allow errors to propagate
228 result = func.apply(null, arguments as unknown as any[])
229 resultsCount++
230 }
231
232 terminatedNode.s = TERMINATED
233
234 if (resultEqualityCheck) {
235 const lastResultValue = lastResult?.deref?.() ?? lastResult
236 if (
237 lastResultValue != null &&
238 resultEqualityCheck(lastResultValue as ReturnType<Func>, result)
239 ) {
240 result = lastResultValue
241 resultsCount !== 0 && resultsCount--
242 }
243
244 const needsWeakRef =
245 (typeof result === 'object' && result !== null) ||
246 typeof result === 'function'
247 lastResult = needsWeakRef ? new Ref(result) : result
248 }
249 terminatedNode.v = result
250 return result
251 }
252
253 memoized.clearCache = () => {
254 fnNode = createCacheNode()
255 memoized.resetResultsCount()
256 }
257
258 memoized.resultsCount = () => resultsCount
259
260 memoized.resetResultsCount = () => {
261 resultsCount = 0
262 }
263
264 return memoized as Func & Simplify<DefaultMemoizeFields>
265}
Note: See TracBrowser for help on using the repository browser.