10
10
import type { ReactContext } from 'shared/ReactTypes' ;
11
11
import type { FiberRoot } from './ReactInternalTypes' ;
12
12
import type { Lanes } from './ReactFiberLane.new' ;
13
+ import type { StackCursor } from './ReactFiberStack.new' ;
13
14
14
15
import { enableCache } from 'shared/ReactFeatureFlags' ;
15
16
import { REACT_CONTEXT_TYPE } from 'shared/ReactSymbols' ;
16
- import { HostRoot } from './ReactWorkTags' ;
17
17
18
+ import { isPrimaryRenderer } from './ReactFiberHostConfig' ;
19
+ import { createCursor , push , pop } from './ReactFiberStack.new' ;
18
20
import { pushProvider , popProvider } from './ReactFiberNewContext.new' ;
19
21
20
22
export type Cache = Map < ( ) => mixed , mixed > ;
21
23
22
- export type SuspendedCacheFresh = { |
23
- tag : 0 ,
24
- cache : Cache ,
24
+ export type CacheComponentState = { |
25
+ + parent : Cache ,
26
+ + cache : Cache ,
25
27
| } ;
26
28
27
- export type SuspendedCachePool = { |
28
- tag : 1 ,
29
- cache : Cache ,
29
+ export type SpawnedCachePool = { |
30
+ + parent : Cache ,
31
+ + pool : Cache ,
30
32
| } ;
31
33
32
- export type SuspendedCache = SuspendedCacheFresh | SuspendedCachePool ;
33
-
34
- export const SuspendedCacheFreshTag = 0 ;
35
- export const SuspendedCachePoolTag = 1 ;
36
-
37
34
export const CacheContext : ReactContext < Cache > = enableCache
38
35
? {
39
36
$$typeof : REACT_CONTEXT_TYPE ,
@@ -53,77 +50,28 @@ if (__DEV__ && enableCache) {
53
50
CacheContext . _currentRenderer2 = null ;
54
51
}
55
52
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.
61
55
let pooledCache : Cache | null = null ;
62
56
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 ) ;
76
60
77
- export function pushFreshCacheProvider ( workInProgress : Fiber , cache : Cache ) {
61
+ export function pushCacheProvider ( workInProgress : Fiber , cache : Cache ) {
78
62
if ( ! enableCache ) {
79
63
return ;
80
64
}
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 ;
95
65
pushProvider ( workInProgress , CacheContext , cache ) ;
96
66
}
97
67
98
68
export function popCacheProvider ( workInProgress : Fiber , cache : Cache ) {
99
69
if ( ! enableCache ) {
100
70
return ;
101
71
}
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 ;
110
72
popProvider ( CacheContext , workInProgress ) ;
111
73
}
112
74
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
-
127
75
export function requestCacheFromPool ( renderLanes : Lanes ) : Cache {
128
76
if ( ! enableCache ) {
129
77
return ( null : any ) ;
@@ -136,10 +84,6 @@ export function requestCacheFromPool(renderLanes: Lanes): Cache {
136
84
return pooledCache ;
137
85
}
138
86
139
- export function getPooledCacheIfExists ( ) : Cache | null {
140
- return pooledCache ;
141
- }
142
-
143
87
export function pushRootCachePool ( root : FiberRoot ) {
144
88
if ( ! enableCache ) {
145
89
return ;
@@ -161,37 +105,100 @@ export function popRootCachePool(root: FiberRoot, renderLanes: Lanes) {
161
105
// once all the transitions that depend on it (which we track with
162
106
// `pooledCacheLanes`) have committed.
163
107
root . pooledCache = pooledCache ;
164
- root . pooledCacheLanes |= renderLanes ;
108
+ if ( pooledCache !== null ) {
109
+ root . pooledCacheLanes |= renderLanes ;
110
+ }
165
111
}
166
112
167
- export function pushCachePool ( suspendedCache : SuspendedCachePool ) {
113
+ export function restoreSpawnedCachePool (
114
+ offscreenWorkInProgress : Fiber ,
115
+ prevCachePool : SpawnedCachePool ,
116
+ ) : SpawnedCachePool | null {
168
117
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 ;
170
137
}
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 ) ;
183
138
}
184
139
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 ) {
186
147
if ( ! enableCache ) {
187
148
return ;
188
149
}
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 ;
193
173
}
194
174
}
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
+ } ;
197
204
}
0 commit comments