main
js 320 lines 9.11 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 /* eslint-disable react-internal/prod-error-codes */
11
12 import type {ReactElement} from 'shared/ReactElementType';
13 import type {Fiber, FiberRoot} from './ReactInternalTypes';
14 import type {ReactNodeList} from 'shared/ReactTypes';
15
16 import {
17 flushSyncWork,
18 scheduleUpdateOnFiber,
19 flushPendingEffects,
20 } from './ReactFiberWorkLoop';
21 import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates';
22 import {updateContainerSync} from './ReactFiberReconciler';
23 import {emptyContextObject} from './ReactFiberLegacyContext';
24 import {SyncLane} from './ReactFiberLane';
25 import {
26 ClassComponent,
27 FunctionComponent,
28 ForwardRef,
29 MemoComponent,
30 SimpleMemoComponent,
31 } from './ReactWorkTags';
32 import {
33 REACT_FORWARD_REF_TYPE,
34 REACT_MEMO_TYPE,
35 REACT_LAZY_TYPE,
36 } from 'shared/ReactSymbols';
37
38 export type Family = {
39 current: any,
40 };
41
42 export type RefreshUpdate = {
43 staleFamilies: Set<Family>,
44 updatedFamilies: Set<Family>,
45 };
46
47 // Resolves type to a family.
48 type RefreshHandler = any => Family | void;
49
50 // Used by React Refresh runtime through DevTools Global Hook.
51 export type SetRefreshHandler = (handler: RefreshHandler | null) => void;
52 export type ScheduleRefresh = (root: FiberRoot, update: RefreshUpdate) => void;
53 export type ScheduleRoot = (root: FiberRoot, element: ReactNodeList) => void;
54
55 let resolveFamily: RefreshHandler | null = null;
56 let failedBoundaries: WeakSet<Fiber> | null = null;
57
58 export const setRefreshHandler = (handler: RefreshHandler | null): void => {
59 if (__DEV__) {
60 resolveFamily = handler;
61 }
62 };
63
64 export function resolveTypeForHotReloading(type: any): any {
65 if (__DEV__) {
66 if (resolveFamily === null) {
67 // Hot reloading is disabled.
68 return type;
69 }
70 const family = resolveFamily(type);
71 if (family === undefined) {
72 return type;
73 }
74 // Use the latest known implementation.
75 return family.current;
76 } else {
77 return type;
78 }
79 }
80
81 export function isCompatibleFamilyForHotReloading(
82 fiber: Fiber,
83 element: ReactElement,
84 ): boolean {
85 if (__DEV__) {
86 if (resolveFamily === null) {
87 // Hot reloading is disabled.
88 return false;
89 }
90 const resolve = resolveFamily;
91
92 const prevType = fiber.elementType;
93 const nextType = element.type;
94
95 // If we got here, we know types aren't === equal.
96 let needsCompareFamilies = false;
97
98 const $$typeofNextType =
99 typeof nextType === 'object' && nextType !== null
100 ? nextType.$$typeof
101 : null;
102
103 switch (fiber.tag) {
104 case ClassComponent: {
105 if (typeof nextType === 'function') {
106 needsCompareFamilies = true;
107 }
108 break;
109 }
110 case FunctionComponent: {
111 if (typeof nextType === 'function') {
112 needsCompareFamilies = true;
113 } else if ($$typeofNextType === REACT_LAZY_TYPE) {
114 // We don't know the inner type yet.
115 // We're going to assume that the lazy inner type is stable,
116 // and so it is sufficient to avoid reconciling it away.
117 // We're not going to unwrap or actually use the new lazy type.
118 needsCompareFamilies = true;
119 }
120 break;
121 }
122 case ForwardRef: {
123 if ($$typeofNextType === REACT_FORWARD_REF_TYPE) {
124 needsCompareFamilies = true;
125 } else if ($$typeofNextType === REACT_LAZY_TYPE) {
126 needsCompareFamilies = true;
127 }
128 break;
129 }
130 case MemoComponent:
131 case SimpleMemoComponent: {
132 if ($$typeofNextType === REACT_MEMO_TYPE) {
133 needsCompareFamilies = true;
134 } else if ($$typeofNextType === REACT_LAZY_TYPE) {
135 needsCompareFamilies = true;
136 }
137 break;
138 }
139 default:
140 return false;
141 }
142
143 // Check if both types have a family and it's the same one.
144 if (needsCompareFamilies) {
145 // Note: memo() and forwardRef() we'll compare outer rather than inner type.
146 // This means both of them need to be registered to preserve state.
147 // If we unwrapped and compared the inner types for wrappers instead,
148 // then we would risk falsely saying two separate memo(Foo)
149 // calls are equivalent because they wrap the same Foo function.
150 const prevFamily = resolve(prevType);
151 if (prevFamily !== undefined && prevFamily === resolve(nextType)) {
152 return true;
153 }
154 }
155 return false;
156 } else {
157 return false;
158 }
159 }
160
161 export function markFailedErrorBoundaryForHotReloading(fiber: Fiber) {
162 if (__DEV__) {
163 if (resolveFamily === null) {
164 // Hot reloading is disabled.
165 return;
166 }
167 if (typeof WeakSet !== 'function') {
168 return;
169 }
170 if (failedBoundaries === null) {
171 failedBoundaries = new WeakSet();
172 }
173 failedBoundaries.add(fiber);
174 }
175 }
176
177 export const scheduleRefresh: ScheduleRefresh = (
178 root: FiberRoot,
179 update: RefreshUpdate,
180 ): void => {
181 if (__DEV__) {
182 if (resolveFamily === null) {
183 // Hot reloading is disabled.
184 return;
185 }
186 const {staleFamilies, updatedFamilies} = update;
187 flushPendingEffects();
188 scheduleFibersWithFamiliesRecursively(
189 root.current,
190 updatedFamilies,
191 staleFamilies,
192 );
193 flushSyncWork();
194 }
195 };
196
197 export const scheduleRoot: ScheduleRoot = (
198 root: FiberRoot,
199 element: ReactNodeList,
200 ): void => {
201 if (__DEV__) {
202 if (root.context !== emptyContextObject) {
203 // Super edge case: root has a legacy _renderSubtree context
204 // but we don't know the parentComponent so we can't pass it.
205 // Just ignore. We'll delete this with _renderSubtree code path later.
206 return;
207 }
208 updateContainerSync(element, root, null, null);
209 flushSyncWork();
210 }
211 };
212
213 function scheduleFibersWithFamiliesRecursively(
214 fiber: Fiber,
215 updatedFamilies: Set<Family>,
216 staleFamilies: Set<Family>,
217 ): void {
218 if (__DEV__) {
219 do {
220 const {alternate, child, sibling, tag, type, elementType} = fiber;
221
222 let candidateType = null;
223 // Wrapper fibers (memo, forwardRef) resolve their family through the
224 // inner implementation, but an edit that changes the kind of the type
225 // (e.g. memo to a plain function) is only recorded on the family of the
226 // outer type. Check the outer type too so such edits trigger a remount.
227 let outerCandidateType = null;
228 switch (tag) {
229 case FunctionComponent:
230 case ClassComponent:
231 candidateType = type;
232 break;
233 case SimpleMemoComponent:
234 candidateType = type;
235 outerCandidateType = elementType;
236 break;
237 case MemoComponent:
238 // Edits to the inner implementation are handled by the inner fiber.
239 outerCandidateType = elementType;
240 break;
241 case ForwardRef:
242 candidateType = type.render;
243 outerCandidateType = elementType;
244 break;
245 default:
246 break;
247 }
248
249 if (resolveFamily === null) {
250 throw new Error('Expected resolveFamily to be set during hot reload.');
251 }
252 const resolve = resolveFamily;
253
254 let needsRender = false;
255 let needsRemount = false;
256 if (candidateType !== null) {
257 const family = resolve(candidateType);
258 if (family !== undefined) {
259 if (staleFamilies.has(family)) {
260 needsRemount = true;
261 } else if (updatedFamilies.has(family)) {
262 if (tag === ClassComponent) {
263 needsRemount = true;
264 } else {
265 needsRender = true;
266 }
267 }
268 }
269 }
270 if (!needsRemount && outerCandidateType !== null) {
271 const outerFamily = resolve(outerCandidateType);
272 if (outerFamily !== undefined && staleFamilies.has(outerFamily)) {
273 needsRemount = true;
274 } else if (
275 typeof outerCandidateType === 'object' &&
276 outerCandidateType.$$typeof === REACT_LAZY_TYPE
277 ) {
278 const payload = outerCandidateType._payload;
279 if (payload._status === 1 /* Resolved; see ReactLazy */) {
280 const middleFamily = resolve(payload._result.default);
281 if (middleFamily !== undefined && staleFamilies.has(middleFamily)) {
282 needsRemount = true;
283 }
284 }
285 }
286 }
287 if (failedBoundaries !== null) {
288 if (
289 failedBoundaries.has(fiber) ||
290 // $FlowFixMe[incompatible-use] found when upgrading Flow
291 (alternate !== null && failedBoundaries.has(alternate))
292 ) {
293 needsRemount = true;
294 }
295 }
296
297 if (needsRemount) {
298 fiber._debugNeedsRemount = true;
299 }
300 if (needsRemount || needsRender) {
301 const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
302 if (root !== null) {
303 scheduleUpdateOnFiber(root, fiber, SyncLane);
304 }
305 }
306 if (child !== null && !needsRemount) {
307 scheduleFibersWithFamiliesRecursively(
308 child,
309 updatedFamilies,
310 staleFamilies,
311 );
312 }
313
314 if (sibling === null) {
315 break;
316 }
317 fiber = sibling;
318 } while (true);
319 }
320 }