0
# Memoization Strategies
1
2
Multiple memoization implementations optimized for different use cases and performance characteristics.
3
4
## Capabilities
5
6
### lruMemoize
7
8
LRU (Least Recently Used) cache-based memoization with configurable cache size and equality checking.
9
10
```typescript { .api }
11
/**
12
* Creates a memoized function using LRU caching strategy
13
* @param func - Function to memoize
14
* @param options - Configuration options for LRU cache
15
* @returns Memoized function with clearCache method
16
*/
17
function lruMemoize<Args extends readonly unknown[], Return>(
18
func: (...args: Args) => Return,
19
options?: LruMemoizeOptions
20
): ((...args: Args) => Return) & DefaultMemoizeFields;
21
22
interface LruMemoizeOptions<Result = any> {
23
/** Maximum number of results to cache (default: 1) */
24
maxSize?: number;
25
26
/** Function to check equality of cache keys (default: referenceEqualityCheck) */
27
equalityCheck?: EqualityFn;
28
29
/** Function to compare newly generated output value against cached values */
30
resultEqualityCheck?: EqualityFn<Result>;
31
}
32
```
33
34
**Basic Usage:**
35
36
```typescript
37
import { lruMemoize } from "reselect";
38
39
// Simple memoization with default options
40
const memoizedExpensiveFunction = lruMemoize((data) => {
41
return performExpensiveComputation(data);
42
});
43
44
// With custom cache size
45
const memoizedWithLargerCache = lruMemoize(
46
(data) => processData(data),
47
{ maxSize: 10 }
48
);
49
50
// Usage
51
const result = memoizedExpensiveFunction(someData);
52
console.log(memoizedExpensiveFunction.resultsCount()); // 1
53
54
// Call again with same data (should not recompute)
55
const result2 = memoizedExpensiveFunction(someData);
56
console.log(memoizedExpensiveFunction.resultsCount()); // Still 1
57
58
// Call with different data
59
const result3 = memoizedExpensiveFunction(otherData);
60
console.log(memoizedExpensiveFunction.resultsCount()); // 2
61
62
// Reset results count
63
memoizedExpensiveFunction.resetResultsCount();
64
console.log(memoizedExpensiveFunction.resultsCount()); // 0
65
66
// Clear the cache
67
memoizedExpensiveFunction.clearCache();
68
```
69
70
**With Custom Equality Check:**
71
72
```typescript
73
import { lruMemoize } from "reselect";
74
75
// Custom equality check for objects
76
const deepEqualMemoized = lruMemoize(
77
(obj) => transformObject(obj),
78
{
79
maxSize: 5,
80
equalityCheck: (a, b) => JSON.stringify(a) === JSON.stringify(b)
81
}
82
);
83
84
// Custom equality for specific object properties
85
const userMemoized = lruMemoize(
86
(user) => processUser(user),
87
{
88
equalityCheck: (a, b) => a.id === b.id && a.version === b.version
89
}
90
);
91
92
// With result equality check to handle cases where input changes but output is the same
93
const todoIdsMemoized = lruMemoize(
94
(todos) => todos.map(todo => todo.id),
95
{
96
maxSize: 3,
97
resultEqualityCheck: (a, b) =>
98
a.length === b.length && a.every((id, index) => id === b[index])
99
}
100
);
101
```
102
103
### referenceEqualityCheck
104
105
Default equality function used by `lruMemoize` for comparing cache keys.
106
107
```typescript { .api }
108
/**
109
* Reference equality check function (===)
110
* @param a - First value to compare
111
* @param b - Second value to compare
112
* @returns True if values are reference equal
113
*/
114
function referenceEqualityCheck(a: any, b: any): boolean;
115
```
116
117
### weakMapMemoize
118
119
WeakMap-based memoization that automatically garbage collects unused cache entries when objects are no longer referenced.
120
121
```typescript { .api }
122
/**
123
* Creates a memoized function using WeakMap caching strategy
124
* @param func - Function to memoize
125
* @param options - Configuration options for WeakMap cache
126
* @returns Memoized function with clearCache method
127
*/
128
function weakMapMemoize<Args extends readonly unknown[], Return>(
129
func: (...args: Args) => Return,
130
options?: WeakMapMemoizeOptions
131
): ((...args: Args) => Return) & DefaultMemoizeFields;
132
133
interface WeakMapMemoizeOptions<Result = any> {
134
/** Function to compare newly generated output value against cached values */
135
resultEqualityCheck?: EqualityFn<Result>;
136
}
137
```
138
139
**Basic Usage:**
140
141
```typescript
142
import { weakMapMemoize } from "reselect";
143
144
// WeakMap memoization (default for createSelector)
145
const memoizedProcessor = weakMapMemoize((objects) => {
146
return objects.map(obj => processObject(obj));
147
});
148
149
// Check results count
150
const result = memoizedProcessor(someObjects);
151
console.log(memoizedProcessor.resultsCount()); // 1
152
153
// Clear cache and reset count
154
memoizedProcessor.clearCache(); // Also resets results count
155
console.log(memoizedProcessor.resultsCount()); // 0
156
157
// With result equality check
158
const customWeakMapMemoized = weakMapMemoize(
159
(data) => transformData(data),
160
{
161
resultEqualityCheck: (a, b) => a.length === b.length && a.every((item, i) => item.id === b[i].id)
162
}
163
);
164
```
165
166
**Automatic Garbage Collection:**
167
168
```typescript
169
import { weakMapMemoize } from "reselect";
170
171
const processObjects = weakMapMemoize((objectArray) => {
172
return objectArray.map(obj => expensiveTransform(obj));
173
});
174
175
// Objects are automatically garbage collected when no longer referenced
176
let objects = [{ id: 1 }, { id: 2 }];
177
const result1 = processObjects(objects); // Cached
178
179
objects = null; // Original objects can be garbage collected
180
// Cache entries for those objects are automatically cleaned up
181
```
182
183
### unstable_autotrackMemoize
184
185
Experimental auto-tracking memoization using Proxy to track nested field access patterns.
186
187
```typescript { .api }
188
/**
189
* Experimental memoization that tracks which nested fields are accessed
190
* @param func - Function to memoize
191
* @returns Memoized function with clearCache method
192
*/
193
function unstable_autotrackMemoize<Func extends AnyFunction>(
194
func: Func
195
): Func & DefaultMemoizeFields;
196
```
197
198
**Basic Usage:**
199
200
```typescript
201
import { unstable_autotrackMemoize } from "reselect";
202
203
// Auto-tracking memoization
204
const autotrackProcessor = unstable_autotrackMemoize((state) => {
205
// Only recomputes if state.users[0].profile.name changes
206
return state.users[0].profile.name.toUpperCase();
207
});
208
209
// With createSelector
210
import { createSelector } from "reselect";
211
212
const selectUserName = createSelector(
213
[(state) => state.users],
214
(users) => users[0]?.profile?.name, // Tracks specific field access
215
{ memoize: unstable_autotrackMemoize }
216
);
217
```
218
219
**Design Tradeoffs:**
220
221
- **Pros**: More precise memoization, avoids excess calculations, fewer re-renders
222
- **Cons**: Cache size of 1, slower than lruMemoize, unexpected behavior with non-accessing selectors
223
- **Use Case**: Nested field access where you want to avoid recomputation when unrelated fields change
224
225
**Important Limitations:**
226
227
```typescript
228
// This selector will NEVER update because it doesn't access any fields
229
const badSelector = createSelector(
230
[(state) => state.todos],
231
(todos) => todos, // Just returns the value directly - no field access
232
{ memoize: unstable_autotrackMemoize }
233
);
234
235
// This works correctly because it accesses fields
236
const goodSelector = createSelector(
237
[(state) => state.todos],
238
(todos) => todos.map(todo => todo.id), // Accesses .map and .id
239
{ memoize: unstable_autotrackMemoize }
240
);
241
```
242
243
## Performance Comparison
244
245
### When to Use Each Strategy
246
247
**lruMemoize:**
248
- Multiple argument combinations need caching
249
- Predictable cache eviction behavior needed
250
- Working with primitive values or need custom equality logic
251
- Default choice for most use cases
252
253
**weakMapMemoize:**
254
- Working primarily with object references
255
- Want automatic garbage collection
256
- Large numbers of different object combinations
257
- Memory efficiency is important
258
259
**unstable_autotrackMemoize:**
260
- Accessing specific nested fields in large objects
261
- Want to avoid recomputation when unrelated fields change
262
- Can accept cache size limitation of 1
263
- Performance testing shows it's beneficial for your use case
264
265
### Usage Examples in Selectors
266
267
```typescript
268
import {
269
createSelector,
270
createSelectorCreator,
271
lruMemoize,
272
weakMapMemoize,
273
unstable_autotrackMemoize
274
} from "reselect";
275
276
// LRU memoization for selectors with multiple cache entries
277
const createLRUSelector = createSelectorCreator({
278
memoize: lruMemoize,
279
memoizeOptions: { maxSize: 50 }
280
});
281
282
const selectFilteredItems = createLRUSelector(
283
[selectItems, selectFilters],
284
(items, filters) => applyFilters(items, filters)
285
);
286
287
// WeakMap memoization (default)
288
const selectProcessedUsers = createSelector(
289
[selectUsers],
290
(users) => users.map(user => processUser(user))
291
);
292
293
// Auto-tracking for nested field access
294
const createAutotrackSelector = createSelectorCreator({
295
memoize: unstable_autotrackMemoize
296
});
297
298
const selectSpecificUserData = createAutotrackSelector(
299
[(state) => state],
300
(state) => ({
301
name: state.users.currentUser.profile.displayName,
302
avatar: state.users.currentUser.profile.avatar.url
303
})
304
);
305
```
306
307
## Types
308
309
```typescript { .api }
310
interface DefaultMemoizeFields {
311
/** Clears the memoization cache */
312
clearCache: () => void;
313
/** Returns the number of times the memoized function has computed results */
314
resultsCount: () => number;
315
/** Resets the results count to 0 */
316
resetResultsCount: () => void;
317
}
318
319
type EqualityFn<T = any> = (a: T, b: T) => boolean;
320
321
type AnyFunction = (...args: any[]) => any;
322
323
interface Cache {
324
get(key: unknown): unknown | typeof NOT_FOUND;
325
put(key: unknown, value: unknown): void;
326
getEntries(): Array<{ key: unknown; value: unknown }>;
327
clear(): void;
328
}
329
```