-
Notifications
You must be signed in to change notification settings - Fork 48.7k
useMemoCache implementation #25143
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
useMemoCache implementation #25143
Changes from all commits
841ea33
91112dc
11081d1
120973c
202208e
8bcf523
2900e4d
c20c3a3
90dd08e
fcefa96
e63e02b
32cd35e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,7 +16,12 @@ import type { | |
Usable, | ||
Thenable, | ||
} from 'shared/ReactTypes'; | ||
import type {Fiber, Dispatcher, HookType} from './ReactInternalTypes'; | ||
import type { | ||
Fiber, | ||
Dispatcher, | ||
HookType, | ||
MemoCache, | ||
} from './ReactInternalTypes'; | ||
import type {Lanes, Lane} from './ReactFiberLane.new'; | ||
import type {HookFlags} from './ReactHookEffectTags'; | ||
import type {FiberRoot} from './ReactInternalTypes'; | ||
|
@@ -177,6 +182,8 @@ type StoreConsistencyCheck<T> = { | |
export type FunctionComponentUpdateQueue = { | ||
lastEffect: Effect | null, | ||
stores: Array<StoreConsistencyCheck<any>> | null, | ||
// NOTE: optional, only set when enableUseMemoCacheHook is enabled | ||
memoCache?: MemoCache | null, | ||
}; | ||
|
||
type BasicStateAction<S> = (S => S) | S; | ||
|
@@ -710,10 +717,23 @@ function updateWorkInProgressHook(): Hook { | |
return workInProgressHook; | ||
} | ||
|
||
function createFunctionComponentUpdateQueue(): FunctionComponentUpdateQueue { | ||
return { | ||
lastEffect: null, | ||
stores: null, | ||
// NOTE: defining two versions of this function to avoid size impact when this feature is disabled. | ||
// Previously this function was inlined, the additional `memoCache` property makes it not inlined. | ||
let createFunctionComponentUpdateQueue: () => FunctionComponentUpdateQueue; | ||
if (enableUseMemoCacheHook) { | ||
createFunctionComponentUpdateQueue = () => { | ||
return { | ||
lastEffect: null, | ||
stores: null, | ||
memoCache: null, | ||
}; | ||
}; | ||
} else { | ||
createFunctionComponentUpdateQueue = () => { | ||
return { | ||
lastEffect: null, | ||
stores: null, | ||
}; | ||
}; | ||
} | ||
|
||
|
@@ -787,7 +807,59 @@ function use<T>(usable: Usable<T>): T { | |
} | ||
|
||
function useMemoCache(size: number): Array<any> { | ||
throw new Error('Not implemented.'); | ||
let memoCache = null; | ||
// Fast-path, load memo cache from wip fiber if already prepared | ||
let updateQueue: FunctionComponentUpdateQueue | null = (currentlyRenderingFiber.updateQueue: any); | ||
if (updateQueue !== null) { | ||
memoCache = updateQueue.memoCache; | ||
} | ||
// Otherwise clone from the current fiber | ||
// TODO: not sure how to access the current fiber here other than going through | ||
// currentlyRenderingFiber.alternate | ||
if (memoCache == null) { | ||
const current: Fiber | null = currentlyRenderingFiber.alternate; | ||
if (current !== null) { | ||
const currentUpdateQueue: FunctionComponentUpdateQueue | null = (current.updateQueue: any); | ||
if (currentUpdateQueue !== null) { | ||
const currentMemoCache: ?MemoCache = currentUpdateQueue.memoCache; | ||
if (currentMemoCache != null) { | ||
memoCache = { | ||
data: currentMemoCache.data.map(array => array.slice()), | ||
index: 0, | ||
}; | ||
} | ||
} | ||
} | ||
} | ||
// Finally fall back to allocating a fresh instance of the cache | ||
if (memoCache == null) { | ||
memoCache = { | ||
data: [], | ||
index: 0, | ||
}; | ||
} | ||
if (updateQueue === null) { | ||
updateQueue = createFunctionComponentUpdateQueue(); | ||
currentlyRenderingFiber.updateQueue = updateQueue; | ||
} | ||
updateQueue.memoCache = memoCache; | ||
|
||
let data = memoCache.data[memoCache.index]; | ||
if (data === undefined) { | ||
data = memoCache.data[memoCache.index] = new Array(size); | ||
} else if (data.length !== size) { | ||
// TODO: consider warning or throwing here | ||
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. console.error is usually what we use for internal invariants because it doesn't add runtime overhead in prod |
||
if (__DEV__) { | ||
console.error( | ||
'Expected a constant size argument for each invocation of useMemoCache. ' + | ||
'The previous cache was allocated with size %s but size %s was requested.', | ||
data.length, | ||
size, | ||
); | ||
} | ||
} | ||
memoCache.index++; | ||
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. Do you need this index? Looks like it's always 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. Whenever we initialize a cache we set the index to 0, and each call to useMemoCache increments it. |
||
return data; | ||
} | ||
|
||
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S { | ||
|
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.
This is fine, we don't access it that often in this module so probably not worth stashing in a module-level variable