1010import type { ReactContext } from 'shared/ReactTypes' ;
1111import type { FiberRoot } from './ReactInternalTypes' ;
1212import type { Lanes } from './ReactFiberLane.new' ;
13+ import type { StackCursor } from './ReactFiberStack.new' ;
1314
1415import { enableCache } from 'shared/ReactFeatureFlags' ;
1516import { REACT_CONTEXT_TYPE } from 'shared/ReactSymbols' ;
16- import { HostRoot } from './ReactWorkTags' ;
1717
18+ import { isPrimaryRenderer } from './ReactFiberHostConfig' ;
19+ import { createCursor , push , pop } from './ReactFiberStack.new' ;
1820import { pushProvider , popProvider } from './ReactFiberNewContext.new' ;
1921
2022export type Cache = Map < ( ) => mixed , mixed > ;
2123
22- export type SuspendedCacheFresh = { |
23- tag : 0 ,
24- cache : Cache ,
24+ export type CacheComponentState = { |
25+ + parent : Cache ,
26+ + cache : Cache ,
2527| } ;
2628
27- export type SuspendedCachePool = { |
28- tag : 1 ,
29- cache : Cache ,
29+ export type SpawnedCachePool = { |
30+ + parent : Cache ,
31+ + pool : Cache ,
3032| } ;
3133
32- export type SuspendedCache = SuspendedCacheFresh | SuspendedCachePool ;
33-
34- export const SuspendedCacheFreshTag = 0 ;
35- export const SuspendedCachePoolTag = 1 ;
36-
3734export const CacheContext : ReactContext < Cache > = enableCache
3835 ? {
3936 $$typeof : REACT_CONTEXT_TYPE ,
@@ -53,77 +50,28 @@ if (__DEV__ && enableCache) {
5350 CacheContext . _currentRenderer2 = null ;
5451}
5552
56- // A parent cache refresh always overrides any nested cache. So there will only
57- // ever be a single fresh cache on the context stack.
58- let freshCache : Cache | null = null ;
59-
60- // The cache that we retrived from the pool during this render, if any
53+ // The cache that newly mounted Cache boundaries should use. It's either
54+ // retrieved from the cache pool, or the result of a refresh.
6155let pooledCache : Cache | null = null ;
6256
63- export function pushStaleCacheProvider ( workInProgress : Fiber , cache : Cache ) {
64- if ( ! enableCache ) {
65- return ;
66- }
67- if ( __DEV__ ) {
68- if ( freshCache !== null ) {
69- console . error (
70- 'Already inside a fresh cache boundary. This is a bug in React.' ,
71- ) ;
72- }
73- }
74- pushProvider ( workInProgress , CacheContext , cache ) ;
75- }
57+ // When retrying a Suspense/Offscreen boundary, we override pooledCache with the
58+ // cache from the render that suspended.
59+ const prevFreshCacheOnStack : StackCursor < Cache | null > = createCursor ( null ) ;
7660
77- export function pushFreshCacheProvider ( workInProgress : Fiber , cache : Cache ) {
61+ export function pushCacheProvider ( workInProgress : Fiber , cache : Cache ) {
7862 if ( ! enableCache ) {
7963 return ;
8064 }
81- if ( __DEV__ ) {
82- if (
83- freshCache !== null &&
84- // TODO: Remove this exception for roots. There are a few tests that throw
85- // in pushHostContainer, before the cache context is pushed. Not a huge
86- // issue, but should still fix.
87- workInProgress . tag !== HostRoot
88- ) {
89- console . error (
90- 'Already inside a fresh cache boundary. This is a bug in React.' ,
91- ) ;
92- }
93- }
94- freshCache = cache ;
9565 pushProvider ( workInProgress , CacheContext , cache ) ;
9666}
9767
9868export function popCacheProvider ( workInProgress : Fiber , cache : Cache ) {
9969 if ( ! enableCache ) {
10070 return ;
10171 }
102- if ( __DEV__ ) {
103- if ( freshCache !== null && freshCache !== cache ) {
104- console . error (
105- 'Unexpected cache instance on context. This is a bug in React.' ,
106- ) ;
107- }
108- }
109- freshCache = null ;
11072 popProvider ( CacheContext , workInProgress ) ;
11173}
11274
113- export function hasFreshCacheProvider ( ) {
114- if ( ! enableCache ) {
115- return false ;
116- }
117- return freshCache !== null ;
118- }
119-
120- export function getFreshCacheProviderIfExists ( ) : Cache | null {
121- if ( ! enableCache ) {
122- return null ;
123- }
124- return freshCache ;
125- }
126-
12775export function requestCacheFromPool ( renderLanes : Lanes ) : Cache {
12876 if ( ! enableCache ) {
12977 return ( null : any ) ;
@@ -136,10 +84,6 @@ export function requestCacheFromPool(renderLanes: Lanes): Cache {
13684 return pooledCache ;
13785}
13886
139- export function getPooledCacheIfExists ( ) : Cache | null {
140- return pooledCache ;
141- }
142-
14387export function pushRootCachePool ( root : FiberRoot ) {
14488 if ( ! enableCache ) {
14589 return ;
@@ -161,37 +105,100 @@ export function popRootCachePool(root: FiberRoot, renderLanes: Lanes) {
161105 // once all the transitions that depend on it (which we track with
162106 // `pooledCacheLanes`) have committed.
163107 root . pooledCache = pooledCache ;
164- root . pooledCacheLanes |= renderLanes ;
108+ if ( pooledCache !== null ) {
109+ root . pooledCacheLanes |= renderLanes ;
110+ }
165111}
166112
167- export function pushCachePool ( suspendedCache : SuspendedCachePool ) {
113+ export function restoreSpawnedCachePool (
114+ offscreenWorkInProgress : Fiber ,
115+ prevCachePool : SpawnedCachePool ,
116+ ) : SpawnedCachePool | null {
168117 if ( ! enableCache ) {
169- return ;
118+ return ( null : any ) ;
119+ }
120+ const nextParentCache = isPrimaryRenderer
121+ ? CacheContext . _currentValue
122+ : CacheContext . _currentValue2 ;
123+ if ( nextParentCache !== prevCachePool . parent ) {
124+ // There was a refresh. Don't bother restoring anything since the refresh
125+ // will override it.
126+ return null ;
127+ } else {
128+ // No refresh. Resume with the previous cache. This will override the cache
129+ // pool so that any new Cache boundaries in the subtree use this one instead
130+ // of requesting a fresh one.
131+ push ( prevFreshCacheOnStack , pooledCache , offscreenWorkInProgress ) ;
132+ pooledCache = prevCachePool . pool ;
133+
134+ // Return the cache pool to signal that we did in fact push it. We will
135+ // assign this to the field on the fiber so we know to pop the context.
136+ return prevCachePool ;
170137 }
171- // This will temporarily override the pooled cache for this render, so that
172- // any new Cache boundaries in the subtree use this one. The previous value on
173- // the "stack" is stored on the cache instance. We will restore it during the
174- // complete phase.
175- //
176- // The more straightforward way to do this would be to use the array-based
177- // stack (push/pop). Maybe this is too clever.
178- const prevPooledCacheOnStack = pooledCache ;
179- pooledCache = suspendedCache . cache ;
180- // This is never supposed to be null. I'm cheating. Sorry. It will be reset to
181- // the correct type when we pop.
182- suspendedCache . cache = ( ( prevPooledCacheOnStack : any ) : Cache ) ;
183138}
184139
185- export function popCachePool ( suspendedCache : SuspendedCachePool ) {
140+ // Note: Ideally, `popCachePool` would return this value, and then we would pass
141+ // it to `getSuspendedCachePool`. But factoring reasons, those two functions are
142+ // in different phases/files. They are always called in sequence, though, so we
143+ // can stash the value here temporarily.
144+ let _suspendedPooledCache : Cache | null = null ;
145+
146+ export function popCachePool ( workInProgress : Fiber ) {
186147 if ( ! enableCache ) {
187148 return ;
188149 }
189- const retryCache : Cache = ( pooledCache : any ) ;
190- if ( __DEV__ ) {
191- if ( retryCache === null ) {
192- console . error ( 'Expected to have a pooled cache. This is a bug in React.' ) ;
150+ _suspendedPooledCache = pooledCache ;
151+ pooledCache = prevFreshCacheOnStack . current ;
152+ pop ( prevFreshCacheOnStack , workInProgress ) ;
153+ }
154+
155+ export function getSuspendedCachePool ( ) : SpawnedCachePool | null {
156+ if ( ! enableCache ) {
157+ return null ;
158+ }
159+
160+ // We check the cache on the stack first, since that's the one any new Caches
161+ // would have accessed.
162+ let pool = pooledCache ;
163+ if ( pool === null ) {
164+ // There's no pooled cache above us in the stack. However, a child in the
165+ // suspended tree may have requested a fresh cache pool. If so, we would
166+ // have unwound it with `popCachePool`.
167+ if ( _suspendedPooledCache !== null ) {
168+ pool = _suspendedPooledCache ;
169+ _suspendedPooledCache = null ;
170+ } else {
171+ // There's no suspended cache pool.
172+ return null ;
193173 }
194174 }
195- pooledCache = suspendedCache . cache ;
196- suspendedCache . cache = retryCache ;
175+
176+ return {
177+ // We must also save the parent, so that when we resume we can detect
178+ // a refresh.
179+ parent : isPrimaryRenderer
180+ ? CacheContext . _currentValue
181+ : CacheContext . _currentValue2 ,
182+ pool,
183+ } ;
184+ }
185+
186+ export function getOffscreenDeferredCachePool ( ) : SpawnedCachePool | null {
187+ if ( ! enableCache ) {
188+ return null ;
189+ }
190+
191+ if ( pooledCache === null ) {
192+ // There's no deferred cache pool.
193+ return null ;
194+ }
195+
196+ return {
197+ // We must also store the parent, so that when we resume we can detect
198+ // a refresh.
199+ parent : isPrimaryRenderer
200+ ? CacheContext . _currentValue
201+ : CacheContext . _currentValue2 ,
202+ pool : pooledCache ,
203+ } ;
197204}
0 commit comments