@samitouri / QOS-React / commits / 47b003a828

Resolve host configs at build time (#12792)

* Extract base Jest config This makes it easier to change the source config without affecting the build test config. * Statically import the host config This changes react-reconciler to import HostConfig instead of getting it through a function argument. Rather than start with packages like ReactDOM that want to inline it, I started with React Noop and ensured that *custom* renderers using react-reconciler package still work. To do this, I'm making HostConfig module in the reconciler look at a global variable by default (which, in case of the react-reconciler npm package, ends up being the host config argument in the top-level scope). This is still very broken. * Add scaffolding for importing an inlined renderer * Fix the build * ES exports for renderer methods * ES modules for host configs * Remove closures from the reconciler * Check each renderer's config with Flow * Fix uncovered Flow issue We know nextHydratableInstance doesn't get mutated inside this function, but Flow doesn't so it thinks it may be null. Help Flow. * Prettier * Get rid of enable*Reconciler flags They are not as useful anymore because for almost all cases (except third party renderers) we *know* whether it supports mutation or persistence. This refactoring means react-reconciler and react-reconciler/persistent third-party packages now ship the same thing. Not ideal, but this seems worth how simpler the code becomes. We can later look into addressing it by having a single toggle instead. * Prettier again * Fix Flow config creation issue * Fix imprecise Flow typing * Revert accidental changes

Dan Abramov committed May 19, 2018 at 11:29 UTC 47b003a828fe98e12947ba98e819ec4e617deef1
76 files changed +7832 -7817
packages/react-art/src/ReactART.js
+1 -6
@@ -6,12 +6,11 @@
6 */
7
8 import React from 'react';
9 -import ReactFiberReconciler from 'react-reconciler';
9 +import * as ARTRenderer from 'react-reconciler/inline.art';
10 import Transform from 'art/core/transform';
11 import Mode from 'art/modes/current';
12 import FastNoSideEffects from 'art/modes/fast-noSideEffects';
13
14 -import ReactARTHostConfig from './ReactARTHostConfig';
14 import {TYPES, childrenAsString} from './ReactARTInternals';
15
16 Mode.setCurrent(
@@ -132,10 +131,6 @@ class Text extends React.Component {
131 }
132 }
133
135 -/** ART Renderer */
136 -
137 -const ARTRenderer = ReactFiberReconciler(ReactARTHostConfig);
138 -
134 /** API */
135
136 export const ClippingRectangle = TYPES.CLIPPING_RECTANGLE;
packages/react-art/src/ReactARTHostConfig.js
+141 -131
@@ -234,157 +234,167 @@ function applyTextProps(instance, props, prevProps = {}) {
234 }
235 }
236
237 -const ReactARTHostConfig = {
238 - appendInitialChild(parentInstance, child) {
239 - if (typeof child === 'string') {
240 - // Noop for string children of Text (eg <Text>{'foo'}{'bar'}</Text>)
241 - invariant(false, 'Text children should already be flattened.');
242 - return;
243 - }
237 +export * from 'shared/HostConfigWithNoPersistence';
238 +export * from 'shared/HostConfigWithNoHydration';
239 +
240 +export function appendInitialChild(parentInstance, child) {
241 + if (typeof child === 'string') {
242 + // Noop for string children of Text (eg <Text>{'foo'}{'bar'}</Text>)
243 + invariant(false, 'Text children should already be flattened.');
244 + return;
245 + }
246
245 - child.inject(parentInstance);
246 - },
247 -
248 - createInstance(type, props, internalInstanceHandle) {
249 - let instance;
250 -
251 - switch (type) {
252 - case TYPES.CLIPPING_RECTANGLE:
253 - instance = Mode.ClippingRectangle();
254 - instance._applyProps = applyClippingRectangleProps;
255 - break;
256 - case TYPES.GROUP:
257 - instance = Mode.Group();
258 - instance._applyProps = applyGroupProps;
259 - break;
260 - case TYPES.SHAPE:
261 - instance = Mode.Shape();
262 - instance._applyProps = applyShapeProps;
263 - break;
264 - case TYPES.TEXT:
265 - instance = Mode.Text(
266 - props.children,
267 - props.font,
268 - props.alignment,
269 - props.path,
270 - );
271 - instance._applyProps = applyTextProps;
272 - break;
273 - }
247 + child.inject(parentInstance);
248 +}
249
275 - invariant(instance, 'ReactART does not support the type "%s"', type);
250 +export function createInstance(type, props, internalInstanceHandle) {
251 + let instance;
252 +
253 + switch (type) {
254 + case TYPES.CLIPPING_RECTANGLE:
255 + instance = Mode.ClippingRectangle();
256 + instance._applyProps = applyClippingRectangleProps;
257 + break;
258 + case TYPES.GROUP:
259 + instance = Mode.Group();
260 + instance._applyProps = applyGroupProps;
261 + break;
262 + case TYPES.SHAPE:
263 + instance = Mode.Shape();
264 + instance._applyProps = applyShapeProps;
265 + break;
266 + case TYPES.TEXT:
267 + instance = Mode.Text(
268 + props.children,
269 + props.font,
270 + props.alignment,
271 + props.path,
272 + );
273 + instance._applyProps = applyTextProps;
274 + break;
275 + }
276
277 - instance._applyProps(instance, props);
277 + invariant(instance, 'ReactART does not support the type "%s"', type);
278
279 - return instance;
280 - },
279 + instance._applyProps(instance, props);
280
282 - createTextInstance(text, rootContainerInstance, internalInstanceHandle) {
283 - return text;
284 - },
281 + return instance;
282 +}
283
286 - finalizeInitialChildren(domElement, type, props) {
287 - return false;
288 - },
284 +export function createTextInstance(
285 + text,
286 + rootContainerInstance,
287 + internalInstanceHandle,
288 +) {
289 + return text;
290 +}
291
290 - getPublicInstance(instance) {
291 - return instance;
292 - },
292 +export function finalizeInitialChildren(domElement, type, props) {
293 + return false;
294 +}
295
294 - prepareForCommit() {
295 - // Noop
296 - },
296 +export function getPublicInstance(instance) {
297 + return instance;
298 +}
299
298 - prepareUpdate(domElement, type, oldProps, newProps) {
299 - return UPDATE_SIGNAL;
300 - },
300 +export function prepareForCommit() {
301 + // Noop
302 +}
303
302 - resetAfterCommit() {
303 - // Noop
304 - },
304 +export function prepareUpdate(domElement, type, oldProps, newProps) {
305 + return UPDATE_SIGNAL;
306 +}
307
306 - resetTextContent(domElement) {
307 - // Noop
308 - },
308 +export function resetAfterCommit() {
309 + // Noop
310 +}
311
310 - shouldDeprioritizeSubtree(type, props) {
311 - return false;
312 - },
312 +export function resetTextContent(domElement) {
313 + // Noop
314 +}
315
314 - getRootHostContext() {
315 - return emptyObject;
316 - },
316 +export function shouldDeprioritizeSubtree(type, props) {
317 + return false;
318 +}
319
318 - getChildHostContext() {
319 - return emptyObject;
320 - },
320 +export function getRootHostContext() {
321 + return emptyObject;
322 +}
323
322 - scheduleDeferredCallback: ReactScheduler.scheduleWork,
324 +export function getChildHostContext() {
325 + return emptyObject;
326 +}
327
324 - shouldSetTextContent(type, props) {
325 - return (
326 - typeof props.children === 'string' || typeof props.children === 'number'
327 - );
328 - },
329 -
330 - now: ReactScheduler.now,
331 -
332 - // The ART renderer is secondary to the React DOM renderer.
333 - isPrimaryRenderer: false,
334 -
335 - mutation: {
336 - appendChild(parentInstance, child) {
337 - if (child.parentNode === parentInstance) {
338 - child.eject();
339 - }
340 - child.inject(parentInstance);
341 - },
342 -
343 - appendChildToContainer(parentInstance, child) {
344 - if (child.parentNode === parentInstance) {
345 - child.eject();
346 - }
347 - child.inject(parentInstance);
348 - },
349 -
350 - insertBefore(parentInstance, child, beforeChild) {
351 - invariant(
352 - child !== beforeChild,
353 - 'ReactART: Can not insert node before itself',
354 - );
355 - child.injectBefore(beforeChild);
356 - },
328 +export const scheduleDeferredCallback = ReactScheduler.scheduleWork;
329 +export const cancelDeferredCallback = ReactScheduler.cancelScheduledWork;
330
358 - insertInContainerBefore(parentInstance, child, beforeChild) {
359 - invariant(
360 - child !== beforeChild,
361 - 'ReactART: Can not insert node before itself',
362 - );
363 - child.injectBefore(beforeChild);
364 - },
331 +export function shouldSetTextContent(type, props) {
332 + return (
333 + typeof props.children === 'string' || typeof props.children === 'number'
334 + );
335 +}
336
366 - removeChild(parentInstance, child) {
367 - destroyEventListeners(child);
368 - child.eject();
369 - },
337 +export const now = ReactScheduler.now;
338
371 - removeChildFromContainer(parentInstance, child) {
372 - destroyEventListeners(child);
373 - child.eject();
374 - },
339 +// The ART renderer is secondary to the React DOM renderer.
340 +export const isPrimaryRenderer = false;
341
376 - commitTextUpdate(textInstance, oldText, newText) {
377 - // Noop
378 - },
342 +export const supportsMutation = true;
343
380 - commitMount(instance, type, newProps) {
381 - // Noop
382 - },
344 +export function appendChild(parentInstance, child) {
345 + if (child.parentNode === parentInstance) {
346 + child.eject();
347 + }
348 + child.inject(parentInstance);
349 +}
350
384 - commitUpdate(instance, updatePayload, type, oldProps, newProps) {
385 - instance._applyProps(instance, newProps, oldProps);
386 - },
387 - },
388 -};
351 +export function appendChildToContainer(parentInstance, child) {
352 + if (child.parentNode === parentInstance) {
353 + child.eject();
354 + }
355 + child.inject(parentInstance);
356 +}
357
390 -export default ReactARTHostConfig;
358 +export function insertBefore(parentInstance, child, beforeChild) {
359 + invariant(
360 + child !== beforeChild,
361 + 'ReactART: Can not insert node before itself',
362 + );
363 + child.injectBefore(beforeChild);
364 +}
365 +
366 +export function insertInContainerBefore(parentInstance, child, beforeChild) {
367 + invariant(
368 + child !== beforeChild,
369 + 'ReactART: Can not insert node before itself',
370 + );
371 + child.injectBefore(beforeChild);
372 +}
373 +
374 +export function removeChild(parentInstance, child) {
375 + destroyEventListeners(child);
376 + child.eject();
377 +}
378 +
379 +export function removeChildFromContainer(parentInstance, child) {
380 + destroyEventListeners(child);
381 + child.eject();
382 +}
383 +
384 +export function commitTextUpdate(textInstance, oldText, newText) {
385 + // Noop
386 +}
387 +
388 +export function commitMount(instance, type, newProps) {
389 + // Noop
390 +}
391 +
392 +export function commitUpdate(
393 + instance,
394 + updatePayload,
395 + type,
396 + oldProps,
397 + newProps,
398 +) {
399 + instance._applyProps(instance, newProps, oldProps);
400 +}
packages/react-art/src/__tests__/ReactART-test.js
+22 -16
@@ -15,22 +15,26 @@ const React = require('react');
15 const ReactDOM = require('react-dom');
16 const ReactTestUtils = require('react-dom/test-utils');
17
18 -let Group;
19 -let Shape;
20 -let Surface;
21 -let TestComponent;
22 -
23 -const Missing = {};
18 +// Isolate test renderer.
19 +jest.resetModules();
20 +const ReactTestRenderer = require('react-test-renderer');
21
22 +// Isolate ART renderer.
23 +jest.resetModules();
24 const ReactART = require('react-art');
25 const ARTSVGMode = require('art/modes/svg');
26 const ARTCurrentMode = require('art/modes/current');
28 -
29 -const renderer = require('react-test-renderer');
27 const Circle = require('react-art/Circle');
28 const Rectangle = require('react-art/Rectangle');
29 const Wedge = require('react-art/Wedge');
30
31 +let Group;
32 +let Shape;
33 +let Surface;
34 +let TestComponent;
35 +
36 +const Missing = {};
37 +
38 function testDOMNodeStructure(domNode, expectedStructure) {
39 expect(domNode).toBeDefined();
40 expect(domNode.nodeName).toBe(expectedStructure.nodeName);
@@ -362,7 +366,7 @@ describe('ReactART', () => {
366
367 // Using test renderer instead of the DOM renderer here because async
368 // testing APIs for the DOM renderer don't exist.
365 - const testRenderer = renderer.create(
369 + const testRenderer = ReactTestRenderer.create(
370 <CurrentRendererContext.Provider value="Test">
371 <Yield value="A" />
372 <Yield value="B" />
@@ -397,7 +401,7 @@ describe('ReactART', () => {
401
402 describe('ReactARTComponents', () => {
403 it('should generate a <Shape> with props for drawing the Circle', () => {
400 - const circle = renderer.create(
404 + const circle = ReactTestRenderer.create(
405 <Circle radius={10} stroke="green" strokeWidth={3} fill="blue" />,
406 );
407 expect(circle.toJSON()).toMatchSnapshot();
@@ -405,7 +409,9 @@ describe('ReactARTComponents', () => {
409
410 it('should warn if radius is missing on a Circle component', () => {
411 expect(() =>
408 - renderer.create(<Circle stroke="green" strokeWidth={3} fill="blue" />),
412 + ReactTestRenderer.create(
413 + <Circle stroke="green" strokeWidth={3} fill="blue" />,
414 + ),
415 ).toWarnDev(
416 'Warning: Failed prop type: The prop `radius` is marked as required in `Circle`, ' +
417 'but its value is `undefined`.' +
@@ -414,7 +420,7 @@ describe('ReactARTComponents', () => {
420 });
421
422 it('should generate a <Shape> with props for drawing the Rectangle', () => {
417 - const rectangle = renderer.create(
423 + const rectangle = ReactTestRenderer.create(
424 <Rectangle width={50} height={50} stroke="green" fill="blue" />,
425 );
426 expect(rectangle.toJSON()).toMatchSnapshot();
@@ -422,7 +428,7 @@ describe('ReactARTComponents', () => {
428
429 it('should warn if width/height is missing on a Rectangle component', () => {
430 expect(() =>
425 - renderer.create(<Rectangle stroke="green" fill="blue" />),
431 + ReactTestRenderer.create(<Rectangle stroke="green" fill="blue" />),
432 ).toWarnDev([
433 'Warning: Failed prop type: The prop `width` is marked as required in `Rectangle`, ' +
434 'but its value is `undefined`.' +
@@ -434,21 +440,21 @@ describe('ReactARTComponents', () => {
440 });
441
442 it('should generate a <Shape> with props for drawing the Wedge', () => {
437 - const wedge = renderer.create(
443 + const wedge = ReactTestRenderer.create(
444 <Wedge outerRadius={50} startAngle={0} endAngle={360} fill="blue" />,
445 );
446 expect(wedge.toJSON()).toMatchSnapshot();
447 });
448
449 it('should return null if startAngle equals to endAngle on Wedge', () => {
444 - const wedge = renderer.create(
450 + const wedge = ReactTestRenderer.create(
451 <Wedge outerRadius={50} startAngle={0} endAngle={0} fill="blue" />,
452 );
453 expect(wedge.toJSON()).toBeNull();
454 });
455
456 it('should warn if outerRadius/startAngle/endAngle is missing on a Wedge component', () => {
451 - expect(() => renderer.create(<Wedge fill="blue" />)).toWarnDev([
457 + expect(() => ReactTestRenderer.create(<Wedge fill="blue" />)).toWarnDev([
458 'Warning: Failed prop type: The prop `outerRadius` is marked as required in `Wedge`, ' +
459 'but its value is `undefined`.' +
460 '\n in Wedge (at **)',
packages/react-dom/src/client/ReactDOM.js
+1 -4
@@ -19,7 +19,7 @@ import type {Container} from './ReactDOMHostConfig';
19 import '../shared/checkReact';
20 import './ReactDOMClientInjection';
21
22 -import ReactFiberReconciler from 'react-reconciler';
22 +import * as DOMRenderer from 'react-reconciler/inline.dom';
23 import * as ReactPortal from 'shared/ReactPortal';
24 import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
25 import * as ReactGenericBatching from 'events/ReactGenericBatching';
@@ -35,7 +35,6 @@ import invariant from 'fbjs/lib/invariant';
35 import lowPriorityWarning from 'shared/lowPriorityWarning';
36 import warning from 'fbjs/lib/warning';
37
38 -import ReactDOMHostConfig from './ReactDOMHostConfig';
38 import * as ReactDOMComponentTree from './ReactDOMComponentTree';
39 import * as ReactDOMFiberComponent from './ReactDOMFiberComponent';
40 import * as ReactDOMEventListener from '../events/ReactDOMEventListener';
@@ -447,8 +446,6 @@ function shouldHydrateDueToLegacyHeuristic(container) {
446 );
447 }
448
450 -const DOMRenderer = ReactFiberReconciler(ReactDOMHostConfig);
451 -
449 ReactGenericBatching.injection.injectRenderer(DOMRenderer);
450
451 let warnedAboutHydrateAPI = false;
packages/react-dom/src/client/ReactDOMHostConfig.js
+496 -484
@@ -24,22 +24,26 @@ import {
24 DOCUMENT_FRAGMENT_NODE,
25 } from '../shared/HTMLNodeType';
26
27 -export type Container = Element | Document;
28 -type Props = {
27 +export type Type = string;
28 +export type Props = {
29 autoFocus?: boolean,
30 children?: mixed,
31 hidden?: boolean,
32 suppressHydrationWarning?: boolean,
33 };
34 -type Instance = Element;
35 -type TextInstance = Text;
36 -
34 +export type Container = Element | Document;
35 +export type Instance = Element;
36 +export type TextInstance = Text;
37 +export type HydratableInstance = Element | Text;
38 +export type PublicInstance = Element | Text;
39 type HostContextDev = {
40 namespace: string,
41 ancestorInfo: mixed,
42 };
43 type HostContextProd = string;
42 -type HostContext = HostContextDev | HostContextProd;
44 +export type HostContext = HostContextDev | HostContextProd;
45 +export type UpdatePayload = Array<mixed>;
46 +export type ChildSet = void; // Unused
47
48 const {
49 createElement,
@@ -77,492 +81,500 @@ function shouldAutoFocusHostComponent(type: string, props: Props): boolean {
81 return false;
82 }
83
80 -const ReactDOMHostConfig = {
81 - getRootHostContext(rootContainerInstance: Container): HostContext {
82 - let type;
83 - let namespace;
84 - const nodeType = rootContainerInstance.nodeType;
85 - switch (nodeType) {
86 - case DOCUMENT_NODE:
87 - case DOCUMENT_FRAGMENT_NODE: {
88 - type = nodeType === DOCUMENT_NODE ? '#document' : '#fragment';
89 - let root = (rootContainerInstance: any).documentElement;
90 - namespace = root ? root.namespaceURI : getChildNamespace(null, '');
91 - break;
92 - }
93 - default: {
94 - const container: any =
95 - nodeType === COMMENT_NODE
96 - ? rootContainerInstance.parentNode
97 - : rootContainerInstance;
98 - const ownNamespace = container.namespaceURI || null;
99 - type = container.tagName;
100 - namespace = getChildNamespace(ownNamespace, type);
101 - break;
102 - }
84 +export * from 'shared/HostConfigWithNoPersistence';
85 +
86 +export function getRootHostContext(
87 + rootContainerInstance: Container,
88 +): HostContext {
89 + let type;
90 + let namespace;
91 + const nodeType = rootContainerInstance.nodeType;
92 + switch (nodeType) {
93 + case DOCUMENT_NODE:
94 + case DOCUMENT_FRAGMENT_NODE: {
95 + type = nodeType === DOCUMENT_NODE ? '#document' : '#fragment';
96 + let root = (rootContainerInstance: any).documentElement;
97 + namespace = root ? root.namespaceURI : getChildNamespace(null, '');
98 + break;
99 }
104 - if (__DEV__) {
105 - const validatedTag = type.toLowerCase();
106 - const ancestorInfo = updatedAncestorInfo(null, validatedTag, null);
107 - return {namespace, ancestorInfo};
100 + default: {
101 + const container: any =
102 + nodeType === COMMENT_NODE
103 + ? rootContainerInstance.parentNode
104 + : rootContainerInstance;
105 + const ownNamespace = container.namespaceURI || null;
106 + type = container.tagName;
107 + namespace = getChildNamespace(ownNamespace, type);
108 + break;
109 }
109 - return namespace;
110 - },
111 -
112 - getChildHostContext(
113 - parentHostContext: HostContext,
114 - type: string,
115 - ): HostContext {
116 - if (__DEV__) {
117 - const parentHostContextDev = ((parentHostContext: any): HostContextDev);
118 - const namespace = getChildNamespace(parentHostContextDev.namespace, type);
119 - const ancestorInfo = updatedAncestorInfo(
120 - parentHostContextDev.ancestorInfo,
110 + }
111 + if (__DEV__) {
112 + const validatedTag = type.toLowerCase();
113 + const ancestorInfo = updatedAncestorInfo(null, validatedTag, null);
114 + return {namespace, ancestorInfo};
115 + }
116 + return namespace;
117 +}
118 +
119 +export function getChildHostContext(
120 + parentHostContext: HostContext,
121 + type: string,
122 + rootContainerInstance: Container,
123 +): HostContext {
124 + if (__DEV__) {
125 + const parentHostContextDev = ((parentHostContext: any): HostContextDev);
126 + const namespace = getChildNamespace(parentHostContextDev.namespace, type);
127 + const ancestorInfo = updatedAncestorInfo(
128 + parentHostContextDev.ancestorInfo,
129 + type,
130 + null,
131 + );
132 + return {namespace, ancestorInfo};
133 + }
134 + const parentNamespace = ((parentHostContext: any): HostContextProd);
135 + return getChildNamespace(parentNamespace, type);
136 +}
137 +
138 +export function getPublicInstance(instance: Instance): * {
139 + return instance;
140 +}
141 +
142 +export function prepareForCommit(containerInfo: Container): void {
143 + eventsEnabled = ReactBrowserEventEmitter.isEnabled();
144 + selectionInformation = ReactInputSelection.getSelectionInformation();
145 + ReactBrowserEventEmitter.setEnabled(false);
146 +}
147 +
148 +export function resetAfterCommit(containerInfo: Container): void {
149 + ReactInputSelection.restoreSelection(selectionInformation);
150 + selectionInformation = null;
151 + ReactBrowserEventEmitter.setEnabled(eventsEnabled);
152 + eventsEnabled = null;
153 +}
154 +
155 +export function createInstance(
156 + type: string,
157 + props: Props,
158 + rootContainerInstance: Container,
159 + hostContext: HostContext,
160 + internalInstanceHandle: Object,
161 +): Instance {
162 + let parentNamespace: string;
163 + if (__DEV__) {
164 + // TODO: take namespace into account when validating.
165 + const hostContextDev = ((hostContext: any): HostContextDev);
166 + validateDOMNesting(type, null, hostContextDev.ancestorInfo);
167 + if (
168 + typeof props.children === 'string' ||
169 + typeof props.children === 'number'
170 + ) {
171 + const string = '' + props.children;
172 + const ownAncestorInfo = updatedAncestorInfo(
173 + hostContextDev.ancestorInfo,
174 type,
175 null,
176 );
124 - return {namespace, ancestorInfo};
177 + validateDOMNesting(null, string, ownAncestorInfo);
178 }
126 - const parentNamespace = ((parentHostContext: any): HostContextProd);
127 - return getChildNamespace(parentNamespace, type);
128 - },
129 -
130 - getPublicInstance(instance: Instance | TextInstance): * {
131 - return instance;
132 - },
133 -
134 - prepareForCommit(): void {
135 - eventsEnabled = ReactBrowserEventEmitter.isEnabled();
136 - selectionInformation = ReactInputSelection.getSelectionInformation();
137 - ReactBrowserEventEmitter.setEnabled(false);
138 - },
139 -
140 - resetAfterCommit(): void {
141 - ReactInputSelection.restoreSelection(selectionInformation);
142 - selectionInformation = null;
143 - ReactBrowserEventEmitter.setEnabled(eventsEnabled);
144 - eventsEnabled = null;
145 - },
146 -
147 - createInstance(
148 - type: string,
149 - props: Props,
150 - rootContainerInstance: Container,
151 - hostContext: HostContext,
152 - internalInstanceHandle: Object,
153 - ): Instance {
154 - let parentNamespace: string;
155 - if (__DEV__) {
156 - // TODO: take namespace into account when validating.
157 - const hostContextDev = ((hostContext: any): HostContextDev);
158 - validateDOMNesting(type, null, hostContextDev.ancestorInfo);
159 - if (
160 - typeof props.children === 'string' ||
161 - typeof props.children === 'number'
162 - ) {
163 - const string = '' + props.children;
164 - const ownAncestorInfo = updatedAncestorInfo(
165 - hostContextDev.ancestorInfo,
166 - type,
167 - null,
168 - );
169 - validateDOMNesting(null, string, ownAncestorInfo);
170 - }
171 - parentNamespace = hostContextDev.namespace;
172 - } else {
173 - parentNamespace = ((hostContext: any): HostContextProd);
179 + parentNamespace = hostContextDev.namespace;
180 + } else {
181 + parentNamespace = ((hostContext: any): HostContextProd);
182 + }
183 + const domElement: Instance = createElement(
184 + type,
185 + props,
186 + rootContainerInstance,
187 + parentNamespace,
188 + );
189 + precacheFiberNode(internalInstanceHandle, domElement);
190 + updateFiberProps(domElement, props);
191 + return domElement;
192 +}
193 +
194 +export function appendInitialChild(
195 + parentInstance: Instance,
196 + child: Instance | TextInstance,
197 +): void {
198 + parentInstance.appendChild(child);
199 +}
200 +
201 +export function finalizeInitialChildren(
202 + domElement: Instance,
203 + type: string,
204 + props: Props,
205 + rootContainerInstance: Container,
206 + hostContext: HostContext,
207 +): boolean {
208 + setInitialProperties(domElement, type, props, rootContainerInstance);
209 + return shouldAutoFocusHostComponent(type, props);
210 +}
211 +
212 +export function prepareUpdate(
213 + domElement: Instance,
214 + type: string,
215 + oldProps: Props,
216 + newProps: Props,
217 + rootContainerInstance: Container,
218 + hostContext: HostContext,
219 +): null | Array<mixed> {
220 + if (__DEV__) {
221 + const hostContextDev = ((hostContext: any): HostContextDev);
222 + if (
223 + typeof newProps.children !== typeof oldProps.children &&
224 + (typeof newProps.children === 'string' ||
225 + typeof newProps.children === 'number')
226 + ) {
227 + const string = '' + newProps.children;
228 + const ownAncestorInfo = updatedAncestorInfo(
229 + hostContextDev.ancestorInfo,
230 + type,
231 + null,
232 + );
233 + validateDOMNesting(null, string, ownAncestorInfo);
234 }
175 - const domElement: Instance = createElement(
176 - type,
177 - props,
178 - rootContainerInstance,
179 - parentNamespace,
180 - );
181 - precacheFiberNode(internalInstanceHandle, domElement);
182 - updateFiberProps(domElement, props);
183 - return domElement;
184 - },
185 -
186 - appendInitialChild(
187 - parentInstance: Instance,
188 - child: Instance | TextInstance,
189 - ): void {
190 - parentInstance.appendChild(child);
191 - },
192 -
193 - finalizeInitialChildren(
194 - domElement: Instance,
195 - type: string,
196 - props: Props,
197 - rootContainerInstance: Container,
198 - ): boolean {
199 - setInitialProperties(domElement, type, props, rootContainerInstance);
200 - return shouldAutoFocusHostComponent(type, props);
201 - },
202 -
203 - prepareUpdate(
204 - domElement: Instance,
205 - type: string,
206 - oldProps: Props,
207 - newProps: Props,
208 - rootContainerInstance: Container,
209 - hostContext: HostContext,
210 - ): null | Array<mixed> {
211 - if (__DEV__) {
212 - const hostContextDev = ((hostContext: any): HostContextDev);
213 - if (
214 - typeof newProps.children !== typeof oldProps.children &&
215 - (typeof newProps.children === 'string' ||
216 - typeof newProps.children === 'number')
217 - ) {
218 - const string = '' + newProps.children;
219 - const ownAncestorInfo = updatedAncestorInfo(
220 - hostContextDev.ancestorInfo,
221 - type,
222 - null,
223 - );
224 - validateDOMNesting(null, string, ownAncestorInfo);
225 - }
235 + }
236 + return diffProperties(
237 + domElement,
238 + type,
239 + oldProps,
240 + newProps,
241 + rootContainerInstance,
242 + );
243 +}
244 +
245 +export function shouldSetTextContent(type: string, props: Props): boolean {
246 + return (
247 + type === 'textarea' ||
248 + typeof props.children === 'string' ||
249 + typeof props.children === 'number' ||
250 + (typeof props.dangerouslySetInnerHTML === 'object' &&
251 + props.dangerouslySetInnerHTML !== null &&
252 + typeof props.dangerouslySetInnerHTML.__html === 'string')
253 + );
254 +}
255 +
256 +export function shouldDeprioritizeSubtree(type: string, props: Props): boolean {
257 + return !!props.hidden;
258 +}
259 +
260 +export function createTextInstance(
261 + text: string,
262 + rootContainerInstance: Container,
263 + hostContext: HostContext,
264 + internalInstanceHandle: Object,
265 +): TextInstance {
266 + if (__DEV__) {
267 + const hostContextDev = ((hostContext: any): HostContextDev);
268 + validateDOMNesting(null, text, hostContextDev.ancestorInfo);
269 + }
270 + const textNode: TextInstance = createTextNode(text, rootContainerInstance);
271 + precacheFiberNode(internalInstanceHandle, textNode);
272 + return textNode;
273 +}
274 +
275 +export const now = ReactScheduler.now;
276 +export const isPrimaryRenderer = true;
277 +export const scheduleDeferredCallback = ReactScheduler.scheduleWork;
278 +export const cancelDeferredCallback = ReactScheduler.cancelScheduledWork;
279 +
280 +// -------------------
281 +// Mutation
282 +// -------------------
283 +
284 +export const supportsMutation = true;
285 +
286 +export function commitMount(
287 + domElement: Instance,
288 + type: string,
289 + newProps: Props,
290 + internalInstanceHandle: Object,
291 +): void {
292 + // Despite the naming that might imply otherwise, this method only
293 + // fires if there is an `Update` effect scheduled during mounting.
294 + // This happens if `finalizeInitialChildren` returns `true` (which it
295 + // does to implement the `autoFocus` attribute on the client). But
296 + // there are also other cases when this might happen (such as patching
297 + // up text content during hydration mismatch). So we'll check this again.
298 + if (shouldAutoFocusHostComponent(type, newProps)) {
299 + ((domElement: any):
300 + | HTMLButtonElement
301 + | HTMLInputElement
302 + | HTMLSelectElement
303 + | HTMLTextAreaElement).focus();
304 + }
305 +}
306 +
307 +export function commitUpdate(
308 + domElement: Instance,
309 + updatePayload: Array<mixed>,
310 + type: string,
311 + oldProps: Props,
312 + newProps: Props,
313 + internalInstanceHandle: Object,
314 +): void {
315 + // Update the props handle so that we know which props are the ones with
316 + // with current event handlers.
317 + updateFiberProps(domElement, newProps);
318 + // Apply the diff to the DOM node.
319 + updateProperties(domElement, updatePayload, type, oldProps, newProps);
320 +}
321 +
322 +export function resetTextContent(domElement: Instance): void {
323 + setTextContent(domElement, '');
324 +}
325 +
326 +export function commitTextUpdate(
327 + textInstance: TextInstance,
328 + oldText: string,
329 + newText: string,
330 +): void {
331 + textInstance.nodeValue = newText;
332 +}
333 +
334 +export function appendChild(
335 + parentInstance: Instance,
336 + child: Instance | TextInstance,
337 +): void {
338 + parentInstance.appendChild(child);
339 +}
340 +
341 +export function appendChildToContainer(
342 + container: Container,
343 + child: Instance | TextInstance,
344 +): void {
345 + if (container.nodeType === COMMENT_NODE) {
346 + (container.parentNode: any).insertBefore(child, container);
347 + } else {
348 + container.appendChild(child);
349 + }
350 +}
351 +
352 +export function insertBefore(
353 + parentInstance: Instance,
354 + child: Instance | TextInstance,
355 + beforeChild: Instance | TextInstance,
356 +): void {
357 + parentInstance.insertBefore(child, beforeChild);
358 +}
359 +
360 +export function insertInContainerBefore(
361 + container: Container,
362 + child: Instance | TextInstance,
363 + beforeChild: Instance | TextInstance,
364 +): void {
365 + if (container.nodeType === COMMENT_NODE) {
366 + (container.parentNode: any).insertBefore(child, beforeChild);
367 + } else {
368 + container.insertBefore(child, beforeChild);
369 + }
370 +}
371 +
372 +export function removeChild(
373 + parentInstance: Instance,
374 + child: Instance | TextInstance,
375 +): void {
376 + parentInstance.removeChild(child);
377 +}
378 +
379 +export function removeChildFromContainer(
380 + container: Container,
381 + child: Instance | TextInstance,
382 +): void {
383 + if (container.nodeType === COMMENT_NODE) {
384 + (container.parentNode: any).removeChild(child);
385 + } else {
386 + container.removeChild(child);
387 + }
388 +}
389 +
390 +// -------------------
391 +// Hydration
392 +// -------------------
393 +
394 +export const supportsHydration = true;
395 +
396 +export function canHydrateInstance(
397 + instance: Instance | TextInstance,
398 + type: string,
399 + props: Props,
400 +): null | Instance {
401 + if (
402 + instance.nodeType !== ELEMENT_NODE ||
403 + type.toLowerCase() !== instance.nodeName.toLowerCase()
404 + ) {
405 + return null;
406 + }
407 + // This has now been refined to an element node.
408 + return ((instance: any): Instance);
409 +}
410 +
411 +export function canHydrateTextInstance(
412 + instance: Instance | TextInstance,
413 + text: string,
414 +): null | TextInstance {
415 + if (text === '' || instance.nodeType !== TEXT_NODE) {
416 + // Empty strings are not parsed by HTML so there won't be a correct match here.
417 + return null;
418 + }
419 + // This has now been refined to a text node.
420 + return ((instance: any): TextInstance);
421 +}
422 +
423 +export function getNextHydratableSibling(
424 + instance: Instance | TextInstance,
425 +): null | Instance | TextInstance {
426 + let node = instance.nextSibling;
427 + // Skip non-hydratable nodes.
428 + while (
429 + node &&
430 + node.nodeType !== ELEMENT_NODE &&
431 + node.nodeType !== TEXT_NODE
432 + ) {
433 + node = node.nextSibling;
434 + }
435 + return (node: any);
436 +}
437 +
438 +export function getFirstHydratableChild(
439 + parentInstance: Container | Instance,
440 +): null | Instance | TextInstance {
441 + let next = parentInstance.firstChild;
442 + // Skip non-hydratable nodes.
443 + while (
444 + next &&
445 + next.nodeType !== ELEMENT_NODE &&
446 + next.nodeType !== TEXT_NODE
447 + ) {
448 + next = next.nextSibling;
449 + }
450 + return (next: any);
451 +}
452 +
453 +export function hydrateInstance(
454 + instance: Instance,
455 + type: string,
456 + props: Props,
457 + rootContainerInstance: Container,
458 + hostContext: HostContext,
459 + internalInstanceHandle: Object,
460 +): null | Array<mixed> {
461 + precacheFiberNode(internalInstanceHandle, instance);
462 + // TODO: Possibly defer this until the commit phase where all the events
463 + // get attached.
464 + updateFiberProps(instance, props);
465 + let parentNamespace: string;
466 + if (__DEV__) {
467 + const hostContextDev = ((hostContext: any): HostContextDev);
468 + parentNamespace = hostContextDev.namespace;
469 + } else {
470 + parentNamespace = ((hostContext: any): HostContextProd);
471 + }
472 + return diffHydratedProperties(
473 + instance,
474 + type,
475 + props,
476 + parentNamespace,
477 + rootContainerInstance,
478 + );
479 +}
480 +
481 +export function hydrateTextInstance(
482 + textInstance: TextInstance,
483 + text: string,
484 + internalInstanceHandle: Object,
485 +): boolean {
486 + precacheFiberNode(internalInstanceHandle, textInstance);
487 + return diffHydratedText(textInstance, text);
488 +}
489 +
490 +export function didNotMatchHydratedContainerTextInstance(
491 + parentContainer: Container,
492 + textInstance: TextInstance,
493 + text: string,
494 +) {
495 + if (__DEV__) {
496 + warnForUnmatchedText(textInstance, text);
497 + }
498 +}
499 +
500 +export function didNotMatchHydratedTextInstance(
501 + parentType: string,
502 + parentProps: Props,
503 + parentInstance: Instance,
504 + textInstance: TextInstance,
505 + text: string,
506 +) {
507 + if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
508 + warnForUnmatchedText(textInstance, text);
509 + }
510 +}
511 +
512 +export function didNotHydrateContainerInstance(
513 + parentContainer: Container,
514 + instance: Instance | TextInstance,
515 +) {
516 + if (__DEV__) {
517 + if (instance.nodeType === 1) {
518 + warnForDeletedHydratableElement(parentContainer, (instance: any));
519 + } else {
520 + warnForDeletedHydratableText(parentContainer, (instance: any));
521 }
227 - return diffProperties(
228 - domElement,
229 - type,
230 - oldProps,
231 - newProps,
232 - rootContainerInstance,
233 - );
234 - },
522 + }
523 +}
524
236 - shouldSetTextContent(type: string, props: Props): boolean {
237 - return (
238 - type === 'textarea' ||
239 - typeof props.children === 'string' ||
240 - typeof props.children === 'number' ||
241 - (typeof props.dangerouslySetInnerHTML === 'object' &&
242 - props.dangerouslySetInnerHTML !== null &&
243 - typeof props.dangerouslySetInnerHTML.__html === 'string')
244 - );
245 - },
246 -
247 - shouldDeprioritizeSubtree(type: string, props: Props): boolean {
248 - return !!props.hidden;
249 - },
250 -
251 - createTextInstance(
252 - text: string,
253 - rootContainerInstance: Container,
254 - hostContext: HostContext,
255 - internalInstanceHandle: Object,
256 - ): TextInstance {
257 - if (__DEV__) {
258 - const hostContextDev = ((hostContext: any): HostContextDev);
259 - validateDOMNesting(null, text, hostContextDev.ancestorInfo);
525 +export function didNotHydrateInstance(
526 + parentType: string,
527 + parentProps: Props,
528 + parentInstance: Instance,
529 + instance: Instance | TextInstance,
530 +) {
531 + if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
532 + if (instance.nodeType === 1) {
533 + warnForDeletedHydratableElement(parentInstance, (instance: any));
534 + } else {
535 + warnForDeletedHydratableText(parentInstance, (instance: any));
536 }
261 - const textNode: TextInstance = createTextNode(text, rootContainerInstance);
262 - precacheFiberNode(internalInstanceHandle, textNode);
263 - return textNode;
264 - },
265 -
266 - now: ReactScheduler.now,
267 -
268 - isPrimaryRenderer: true,
269 -
270 - mutation: {
271 - commitMount(
272 - domElement: Instance,
273 - type: string,
274 - newProps: Props,
275 - internalInstanceHandle: Object,
276 - ): void {
277 - // Despite the naming that might imply otherwise, this method only
278 - // fires if there is an `Update` effect scheduled during mounting.
279 - // This happens if `finalizeInitialChildren` returns `true` (which it
280 - // does to implement the `autoFocus` attribute on the client). But
281 - // there are also other cases when this might happen (such as patching
282 - // up text content during hydration mismatch). So we'll check this again.
283 - if (shouldAutoFocusHostComponent(type, newProps)) {
284 - ((domElement: any):
285 - | HTMLButtonElement
286 - | HTMLInputElement
287 - | HTMLSelectElement
288 - | HTMLTextAreaElement).focus();
289 - }
290 - },
291 -
292 - commitUpdate(
293 - domElement: Instance,
294 - updatePayload: Array<mixed>,
295 - type: string,
296 - oldProps: Props,
297 - newProps: Props,
298 - internalInstanceHandle: Object,
299 - ): void {
300 - // Update the props handle so that we know which props are the ones with
301 - // with current event handlers.
302 - updateFiberProps(domElement, newProps);
303 - // Apply the diff to the DOM node.
304 - updateProperties(domElement, updatePayload, type, oldProps, newProps);
305 - },
306 -
307 - resetTextContent(domElement: Instance): void {
308 - setTextContent(domElement, '');
309 - },
310 -
311 - commitTextUpdate(
312 - textInstance: TextInstance,
313 - oldText: string,
314 - newText: string,
315 - ): void {
316 - textInstance.nodeValue = newText;
317 - },
318 -
319 - appendChild(
320 - parentInstance: Instance,
321 - child: Instance | TextInstance,
322 - ): void {
323 - parentInstance.appendChild(child);
324 - },
325 -
326 - appendChildToContainer(
327 - container: Container,
328 - child: Instance | TextInstance,
329 - ): void {
330 - if (container.nodeType === COMMENT_NODE) {
331 - (container.parentNode: any).insertBefore(child, container);
332 - } else {
333 - container.appendChild(child);
334 - }
335 - },
336 -
337 - insertBefore(
338 - parentInstance: Instance,
339 - child: Instance | TextInstance,
340 - beforeChild: Instance | TextInstance,
341 - ): void {
342 - parentInstance.insertBefore(child, beforeChild);
343 - },
344 -
345 - insertInContainerBefore(
346 - container: Container,
347 - child: Instance | TextInstance,
348 - beforeChild: Instance | TextInstance,
349 - ): void {
350 - if (container.nodeType === COMMENT_NODE) {
351 - (container.parentNode: any).insertBefore(child, beforeChild);
352 - } else {
353 - container.insertBefore(child, beforeChild);
354 - }
355 - },
356 -
357 - removeChild(
358 - parentInstance: Instance,
359 - child: Instance | TextInstance,
360 - ): void {
361 - parentInstance.removeChild(child);
362 - },
363 -
364 - removeChildFromContainer(
365 - container: Container,
366 - child: Instance | TextInstance,
367 - ): void {
368 - if (container.nodeType === COMMENT_NODE) {
369 - (container.parentNode: any).removeChild(child);
370 - } else {
371 - container.removeChild(child);
372 - }
373 - },
374 - },
375 -
376 - hydration: {
377 - canHydrateInstance(
378 - instance: Instance | TextInstance,
379 - type: string,
380 - props: Props,
381 - ): null | Instance {
382 - if (
383 - instance.nodeType !== ELEMENT_NODE ||
384 - type.toLowerCase() !== instance.nodeName.toLowerCase()
385 - ) {
386 - return null;
387 - }
388 - // This has now been refined to an element node.
389 - return ((instance: any): Instance);
390 - },
391 -
392 - canHydrateTextInstance(
393 - instance: Instance | TextInstance,
394 - text: string,
395 - ): null | TextInstance {
396 - if (text === '' || instance.nodeType !== TEXT_NODE) {
397 - // Empty strings are not parsed by HTML so there won't be a correct match here.
398 - return null;
399 - }
400 - // This has now been refined to a text node.
401 - return ((instance: any): TextInstance);
402 - },
403 -
404 - getNextHydratableSibling(
405 - instance: Instance | TextInstance,
406 - ): null | Instance | TextInstance {
407 - let node = instance.nextSibling;
408 - // Skip non-hydratable nodes.
409 - while (
410 - node &&
411 - node.nodeType !== ELEMENT_NODE &&
412 - node.nodeType !== TEXT_NODE
413 - ) {
414 - node = node.nextSibling;
415 - }
416 - return (node: any);
417 - },
418 -
419 - getFirstHydratableChild(
420 - parentInstance: Container | Instance,
421 - ): null | Instance | TextInstance {
422 - let next = parentInstance.firstChild;
423 - // Skip non-hydratable nodes.
424 - while (
425 - next &&
426 - next.nodeType !== ELEMENT_NODE &&
427 - next.nodeType !== TEXT_NODE
428 - ) {
429 - next = next.nextSibling;
430 - }
431 - return (next: any);
432 - },
433 -
434 - hydrateInstance(
435 - instance: Instance,
436 - type: string,
437 - props: Props,
438 - rootContainerInstance: Container,
439 - hostContext: HostContext,
440 - internalInstanceHandle: Object,
441 - ): null | Array<mixed> {
442 - precacheFiberNode(internalInstanceHandle, instance);
443 - // TODO: Possibly defer this until the commit phase where all the events
444 - // get attached.
445 - updateFiberProps(instance, props);
446 - let parentNamespace: string;
447 - if (__DEV__) {
448 - const hostContextDev = ((hostContext: any): HostContextDev);
449 - parentNamespace = hostContextDev.namespace;
450 - } else {
451 - parentNamespace = ((hostContext: any): HostContextProd);
452 - }
453 - return diffHydratedProperties(
454 - instance,
455 - type,
456 - props,
457 - parentNamespace,
458 - rootContainerInstance,
459 - );
460 - },
461 -
462 - hydrateTextInstance(
463 - textInstance: TextInstance,
464 - text: string,
465 - internalInstanceHandle: Object,
466 - ): boolean {
467 - precacheFiberNode(internalInstanceHandle, textInstance);
468 - return diffHydratedText(textInstance, text);
469 - },
470 -
471 - didNotMatchHydratedContainerTextInstance(
472 - parentContainer: Container,
473 - textInstance: TextInstance,
474 - text: string,
475 - ) {
476 - if (__DEV__) {
477 - warnForUnmatchedText(textInstance, text);
478 - }
479 - },
480 -
481 - didNotMatchHydratedTextInstance(
482 - parentType: string,
483 - parentProps: Props,
484 - parentInstance: Instance,
485 - textInstance: TextInstance,
486 - text: string,
487 - ) {
488 - if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
489 - warnForUnmatchedText(textInstance, text);
490 - }
491 - },
492 -
493 - didNotHydrateContainerInstance(
494 - parentContainer: Container,
495 - instance: Instance | TextInstance,
496 - ) {
497 - if (__DEV__) {
498 - if (instance.nodeType === 1) {
499 - warnForDeletedHydratableElement(parentContainer, (instance: any));
500 - } else {
501 - warnForDeletedHydratableText(parentContainer, (instance: any));
502 - }
503 - }
504 - },
505 -
506 - didNotHydrateInstance(
507 - parentType: string,
508 - parentProps: Props,
509 - parentInstance: Instance,
510 - instance: Instance | TextInstance,
511 - ) {
512 - if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
513 - if (instance.nodeType === 1) {
514 - warnForDeletedHydratableElement(parentInstance, (instance: any));
515 - } else {
516 - warnForDeletedHydratableText(parentInstance, (instance: any));
517 - }
518 - }
519 - },
520 -
521 - didNotFindHydratableContainerInstance(
522 - parentContainer: Container,
523 - type: string,
524 - props: Props,
525 - ) {
526 - if (__DEV__) {
527 - warnForInsertedHydratedElement(parentContainer, type, props);
528 - }
529 - },
530 -
531 - didNotFindHydratableContainerTextInstance(
532 - parentContainer: Container,
533 - text: string,
534 - ) {
535 - if (__DEV__) {
536 - warnForInsertedHydratedText(parentContainer, text);
537 - }
538 - },
539 -
540 - didNotFindHydratableInstance(
541 - parentType: string,
542 - parentProps: Props,
543 - parentInstance: Instance,
544 - type: string,
545 - props: Props,
546 - ) {
547 - if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
548 - warnForInsertedHydratedElement(parentInstance, type, props);
549 - }
550 - },
551 -
552 - didNotFindHydratableTextInstance(
553 - parentType: string,
554 - parentProps: Props,
555 - parentInstance: Instance,
556 - text: string,
557 - ) {
558 - if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
559 - warnForInsertedHydratedText(parentInstance, text);
560 - }
561 - },
562 - },
563 -
564 - scheduleDeferredCallback: ReactScheduler.scheduleWork,
565 - cancelDeferredCallback: ReactScheduler.cancelScheduledWork,
566 -};
537 + }
538 +}
539
568 -export default ReactDOMHostConfig;
540 +export function didNotFindHydratableContainerInstance(
541 + parentContainer: Container,
542 + type: string,
543 + props: Props,
544 +) {
545 + if (__DEV__) {
546 + warnForInsertedHydratedElement(parentContainer, type, props);
547 + }
548 +}
549 +
550 +export function didNotFindHydratableContainerTextInstance(
551 + parentContainer: Container,
552 + text: string,
553 +) {
554 + if (__DEV__) {
555 + warnForInsertedHydratedText(parentContainer, text);
556 + }
557 +}
558 +
559 +export function didNotFindHydratableInstance(
560 + parentType: string,
561 + parentProps: Props,
562 + parentInstance: Instance,
563 + type: string,
564 + props: Props,
565 +) {
566 + if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
567 + warnForInsertedHydratedElement(parentInstance, type, props);
568 + }
569 +}
570 +
571 +export function didNotFindHydratableTextInstance(
572 + parentType: string,
573 + parentProps: Props,
574 + parentInstance: Instance,
575 + text: string,
576 +) {
577 + if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
578 + warnForInsertedHydratedText(parentInstance, text);
579 + }
580 +}
packages/react-native-renderer/src/ReactFabric.js
+3 -2
@@ -12,6 +12,8 @@ import type {ReactNodeList} from 'shared/ReactTypes';
12
13 import './ReactFabricInjection';
14
15 +import * as ReactFabricRenderer from 'react-reconciler/inline.fabric';
16 +
17 import * as ReactPortal from 'shared/ReactPortal';
18 import * as ReactGenericBatching from 'events/ReactGenericBatching';
19 import ReactVersion from 'shared/ReactVersion';
@@ -19,7 +21,6 @@ import ReactVersion from 'shared/ReactVersion';
21 import NativeMethodsMixin from './NativeMethodsMixin';
22 import ReactNativeComponent from './ReactNativeComponent';
23 import * as ReactNativeComponentTree from './ReactNativeComponentTree';
22 -import ReactFabricRenderer from './ReactFabricRenderer';
24 import {getInspectorDataForViewTag} from './ReactNativeFiberInspector';
25
26 import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
@@ -64,7 +65,7 @@ function findNodeHandle(componentOrHandle: any): ?number {
65 }
66 if (hostInstance.canonical) {
67 // Fabric
67 - return hostInstance.canonical._nativeTag;
68 + return (hostInstance.canonical: any)._nativeTag;
69 }
70 return hostInstance._nativeTag;
71 }
packages/react-native-renderer/src/ReactFabricHostConfig.js
+243 -233
@@ -33,9 +33,9 @@ import {
33 cloneNodeWithNewChildren,
34 cloneNodeWithNewChildrenAndProps,
35 cloneNodeWithNewProps,
36 - createChildSet,
37 - appendChild,
38 - appendChildToSet,
36 + createChildSet as createChildNodeSet,
37 + appendChild as appendChildNode,
38 + appendChildToSet as appendChildNodeToSet,
39 completeRoot,
40 registerEventHandler,
41 } from 'FabricUIManager';
@@ -47,9 +47,24 @@ import UIManager from 'UIManager';
47 // This means that they never overlap.
48 let nextReactTag = 2;
49
50 -type HostContext = $ReadOnly<{|
50 +type Node = Object;
51 +export type Type = string;
52 +export type Props = Object;
53 +export type Instance = {
54 + node: Node,
55 + canonical: ReactFabricHostComponent,
56 +};
57 +export type TextInstance = {
58 + node: Node,
59 +};
60 +export type HydratableInstance = Instance | TextInstance;
61 +export type PublicInstance = ReactFabricHostComponent;
62 +export type Container = number;
63 +export type ChildSet = Object;
64 +export type HostContext = $ReadOnly<{|
65 isInAParentText: boolean,
66 |}>;
67 +export type UpdatePayload = Object;
68
69 // TODO: Remove this conditional once all changes have propagated.
70 if (registerEventHandler) {
@@ -135,247 +150,242 @@ class ReactFabricHostComponent {
150 // eslint-disable-next-line no-unused-expressions
151 (ReactFabricHostComponent.prototype: NativeMethodsMixinType);
152
138 -type Node = Object;
139 -type ChildSet = Object;
140 -type Container = number;
141 -type Instance = {
142 - node: Node,
143 - canonical: ReactFabricHostComponent,
144 -};
145 -type Props = Object;
146 -type TextInstance = {
147 - node: Node,
148 -};
149 -
150 -const ReactFabricHostConfig = {
151 - appendInitialChild(
152 - parentInstance: Instance,
153 - child: Instance | TextInstance,
154 - ): void {
155 - appendChild(parentInstance.node, child.node);
156 - },
157 -
158 - createInstance(
159 - type: string,
160 - props: Props,
161 - rootContainerInstance: Container,
162 - hostContext: HostContext,
163 - internalInstanceHandle: Object,
164 - ): Instance {
165 - const tag = nextReactTag;
166 - nextReactTag += 2;
153 +export * from 'shared/HostConfigWithNoMutation';
154 +export * from 'shared/HostConfigWithNoHydration';
155
168 - const viewConfig = ReactNativeViewConfigRegistry.get(type);
156 +export function appendInitialChild(
157 + parentInstance: Instance,
158 + child: Instance | TextInstance,
159 +): void {
160 + appendChildNode(parentInstance.node, child.node);
161 +}
162
170 - if (__DEV__) {
171 - for (const key in viewConfig.validAttributes) {
172 - if (props.hasOwnProperty(key)) {
173 - deepFreezeAndThrowOnMutationInDev(props[key]);
174 - }
163 +export function createInstance(
164 + type: string,
165 + props: Props,
166 + rootContainerInstance: Container,
167 + hostContext: HostContext,
168 + internalInstanceHandle: Object,
169 +): Instance {
170 + const tag = nextReactTag;
171 + nextReactTag += 2;
172 +
173 + const viewConfig = ReactNativeViewConfigRegistry.get(type);
174 +
175 + if (__DEV__) {
176 + for (const key in viewConfig.validAttributes) {
177 + if (props.hasOwnProperty(key)) {
178 + deepFreezeAndThrowOnMutationInDev(props[key]);
179 }
180 }
181 + }
182
178 - invariant(
179 - type !== 'RCTView' || !hostContext.isInAParentText,
180 - 'Nesting of <View> within <Text> is not currently supported.',
181 - );
183 + invariant(
184 + type !== 'RCTView' || !hostContext.isInAParentText,
185 + 'Nesting of <View> within <Text> is not currently supported.',
186 + );
187 +
188 + const updatePayload = ReactNativeAttributePayload.create(
189 + props,
190 + viewConfig.validAttributes,
191 + );
192 +
193 + const node = createNode(
194 + tag, // reactTag
195 + viewConfig.uiViewClassName, // viewName
196 + rootContainerInstance, // rootTag
197 + updatePayload, // props
198 + internalInstanceHandle, // internalInstanceHandle
199 + );
200 +
201 + const component = new ReactFabricHostComponent(tag, viewConfig, props);
202 +
203 + return {
204 + node: node,
205 + canonical: component,
206 + };
207 +}
208
183 - const updatePayload = ReactNativeAttributePayload.create(
184 - props,
185 - viewConfig.validAttributes,
186 - );
209 +export function createTextInstance(
210 + text: string,
211 + rootContainerInstance: Container,
212 + hostContext: HostContext,
213 + internalInstanceHandle: Object,
214 +): TextInstance {
215 + invariant(
216 + hostContext.isInAParentText,
217 + 'Text strings must be rendered within a <Text> component.',
218 + );
219 +
220 + const tag = nextReactTag;
221 + nextReactTag += 2;
222 +
223 + const node = createNode(
224 + tag, // reactTag
225 + 'RCTRawText', // viewName
226 + rootContainerInstance, // rootTag
227 + {text: text}, // props
228 + internalInstanceHandle, // instance handle
229 + );
230 +
231 + return {
232 + node: node,
233 + };
234 +}
235
188 - const node = createNode(
189 - tag, // reactTag
190 - viewConfig.uiViewClassName, // viewName
191 - rootContainerInstance, // rootTag
192 - updatePayload, // props
193 - internalInstanceHandle, // internalInstanceHandle
194 - );
236 +export function finalizeInitialChildren(
237 + parentInstance: Instance,
238 + type: string,
239 + props: Props,
240 + rootContainerInstance: Container,
241 + hostContext: HostContext,
242 +): boolean {
243 + return false;
244 +}
245
196 - const component = new ReactFabricHostComponent(tag, viewConfig, props);
197 -
198 - return {
199 - node: node,
200 - canonical: component,
201 - };
202 - },
203 -
204 - createTextInstance(
205 - text: string,
206 - rootContainerInstance: Container,
207 - hostContext: HostContext,
208 - internalInstanceHandle: Object,
209 - ): TextInstance {
210 - invariant(
211 - hostContext.isInAParentText,
212 - 'Text strings must be rendered within a <Text> component.',
213 - );
246 +export function getRootHostContext(
247 + rootContainerInstance: Container,
248 +): HostContext {
249 + return {isInAParentText: false};
250 +}
251 +
252 +export function getChildHostContext(
253 + parentHostContext: HostContext,
254 + type: string,
255 + rootContainerInstance: Container,
256 +): HostContext {
257 + const prevIsInAParentText = parentHostContext.isInAParentText;
258 + const isInAParentText =
259 + type === 'AndroidTextInput' || // Android
260 + type === 'RCTMultilineTextInputView' || // iOS
261 + type === 'RCTSinglelineTextInputView' || // iOS
262 + type === 'RCTText' ||
263 + type === 'RCTVirtualText';
264 +
265 + if (prevIsInAParentText !== isInAParentText) {
266 + return {isInAParentText};
267 + } else {
268 + return parentHostContext;
269 + }
270 +}
271
215 - const tag = nextReactTag;
216 - nextReactTag += 2;
272 +export function getPublicInstance(instance: Instance): * {
273 + return instance.canonical;
274 +}
275
218 - const node = createNode(
219 - tag, // reactTag
220 - 'RCTRawText', // viewName
221 - rootContainerInstance, // rootTag
222 - {text: text}, // props
223 - internalInstanceHandle, // instance handle
224 - );
276 +export function prepareForCommit(containerInfo: Container): void {
277 + // Noop
278 +}
279
226 - return {
227 - node: node,
228 - };
229 - },
280 +export function prepareUpdate(
281 + instance: Instance,
282 + type: string,
283 + oldProps: Props,
284 + newProps: Props,
285 + rootContainerInstance: Container,
286 + hostContext: HostContext,
287 +): null | Object {
288 + const viewConfig = instance.canonical.viewConfig;
289 + const updatePayload = ReactNativeAttributePayload.diff(
290 + oldProps,
291 + newProps,
292 + viewConfig.validAttributes,
293 + );
294 + // TODO: If the event handlers have changed, we need to update the current props
295 + // in the commit phase but there is no host config hook to do it yet.
296 + return updatePayload;
297 +}
298
231 - finalizeInitialChildren(
232 - parentInstance: Instance,
233 - type: string,
234 - props: Props,
235 - rootContainerInstance: Container,
236 - ): boolean {
237 - return false;
238 - },
239 -
240 - getRootHostContext(rootContainerInstance: Container): HostContext {
241 - return {isInAParentText: false};
242 - },
243 -
244 - getChildHostContext(
245 - parentHostContext: HostContext,
246 - type: string,
247 - ): HostContext {
248 - const prevIsInAParentText = parentHostContext.isInAParentText;
249 - const isInAParentText =
250 - type === 'AndroidTextInput' || // Android
251 - type === 'RCTMultilineTextInputView' || // iOS
252 - type === 'RCTSinglelineTextInputView' || // iOS
253 - type === 'RCTText' ||
254 - type === 'RCTVirtualText';
255 -
256 - if (prevIsInAParentText !== isInAParentText) {
257 - return {isInAParentText};
299 +export function resetAfterCommit(containerInfo: Container): void {
300 + // Noop
301 +}
302 +
303 +export function shouldDeprioritizeSubtree(type: string, props: Props): boolean {
304 + return false;
305 +}
306 +
307 +export function shouldSetTextContent(type: string, props: Props): boolean {
308 + // TODO (bvaughn) Revisit this decision.
309 + // Always returning false simplifies the createInstance() implementation,
310 + // But creates an additional child Fiber for raw text children.
311 + // No additional native views are created though.
312 + // It's not clear to me which is better so I'm deferring for now.
313 + // More context @ github.com/facebook/react/pull/8560#discussion_r92111303
314 + return false;
315 +}
316 +
317 +// The Fabric renderer is secondary to the existing React Native renderer.
318 +export const isPrimaryRenderer = false;
319 +export const now = ReactNativeFrameScheduling.now;
320 +export const scheduleDeferredCallback =
321 + ReactNativeFrameScheduling.scheduleDeferredCallback;
322 +export const cancelDeferredCallback =
323 + ReactNativeFrameScheduling.cancelDeferredCallback;
324 +
325 +// -------------------
326 +// Persistence
327 +// -------------------
328 +
329 +export const supportsPersistence = true;
330 +
331 +export function cloneInstance(
332 + instance: Instance,
333 + updatePayload: null | Object,
334 + type: string,
335 + oldProps: Props,
336 + newProps: Props,
337 + internalInstanceHandle: Object,
338 + keepChildren: boolean,
339 + recyclableInstance: null | Instance,
340 +): Instance {
341 + const node = instance.node;
342 + let clone;
343 + if (keepChildren) {
344 + if (updatePayload !== null) {
345 + clone = cloneNodeWithNewProps(
346 + node,
347 + updatePayload,
348 + internalInstanceHandle,
349 + );
350 } else {
259 - return parentHostContext;
351 + clone = cloneNode(node, internalInstanceHandle);
352 }
261 - },
262 -
263 - getPublicInstance(instance: Instance): * {
264 - return instance.canonical;
265 - },
266 -
267 - now: ReactNativeFrameScheduling.now,
268 -
269 - // The Fabric renderer is secondary to the existing React Native renderer.
270 - isPrimaryRenderer: false,
271 -
272 - prepareForCommit(): void {
273 - // Noop
274 - },
275 -
276 - prepareUpdate(
277 - instance: Instance,
278 - type: string,
279 - oldProps: Props,
280 - newProps: Props,
281 - rootContainerInstance: Container,
282 - hostContext: HostContext,
283 - ): null | Object {
284 - const viewConfig = instance.canonical.viewConfig;
285 - const updatePayload = ReactNativeAttributePayload.diff(
286 - oldProps,
287 - newProps,
288 - viewConfig.validAttributes,
289 - );
290 - // TODO: If the event handlers have changed, we need to update the current props
291 - // in the commit phase but there is no host config hook to do it yet.
292 - return updatePayload;
293 - },
294 -
295 - resetAfterCommit(): void {
296 - // Noop
297 - },
298 -
299 - scheduleDeferredCallback: ReactNativeFrameScheduling.scheduleDeferredCallback,
300 - cancelDeferredCallback: ReactNativeFrameScheduling.cancelDeferredCallback,
301 -
302 - shouldDeprioritizeSubtree(type: string, props: Props): boolean {
303 - return false;
304 - },
305 -
306 - shouldSetTextContent(type: string, props: Props): boolean {
307 - // TODO (bvaughn) Revisit this decision.
308 - // Always returning false simplifies the createInstance() implementation,
309 - // But creates an additional child Fiber for raw text children.
310 - // No additional native views are created though.
311 - // It's not clear to me which is better so I'm deferring for now.
312 - // More context @ github.com/facebook/react/pull/8560#discussion_r92111303
313 - return false;
314 - },
315 -
316 - persistence: {
317 - cloneInstance(
318 - instance: Instance,
319 - updatePayload: null | Object,
320 - type: string,
321 - oldProps: Props,
322 - newProps: Props,
323 - internalInstanceHandle: Object,
324 - keepChildren: boolean,
325 - recyclableInstance: null | Instance,
326 - ): Instance {
327 - const node = instance.node;
328 - let clone;
329 - if (keepChildren) {
330 - if (updatePayload !== null) {
331 - clone = cloneNodeWithNewProps(
332 - node,
333 - updatePayload,
334 - internalInstanceHandle,
335 - );
336 - } else {
337 - clone = cloneNode(node, internalInstanceHandle);
338 - }
339 - } else {
340 - if (updatePayload !== null) {
341 - clone = cloneNodeWithNewChildrenAndProps(
342 - node,
343 - updatePayload,
344 - internalInstanceHandle,
345 - );
346 - } else {
347 - clone = cloneNodeWithNewChildren(node, internalInstanceHandle);
348 - }
349 - }
350 - return {
351 - node: clone,
352 - canonical: instance.canonical,
353 - };
354 - },
355 -
356 - createContainerChildSet(container: Container): ChildSet {
357 - return createChildSet(container);
358 - },
359 -
360 - appendChildToContainerChildSet(
361 - childSet: ChildSet,
362 - child: Instance | TextInstance,
363 - ): void {
364 - appendChildToSet(childSet, child.node);
365 - },
366 -
367 - finalizeContainerChildren(
368 - container: Container,
369 - newChildren: ChildSet,
370 - ): void {
371 - completeRoot(container, newChildren);
372 - },
373 -
374 - replaceContainerChildren(
375 - container: Container,
376 - newChildren: ChildSet,
377 - ): void {},
378 - },
379 -};
353 + } else {
354 + if (updatePayload !== null) {
355 + clone = cloneNodeWithNewChildrenAndProps(
356 + node,
357 + updatePayload,
358 + internalInstanceHandle,
359 + );
360 + } else {
361 + clone = cloneNodeWithNewChildren(node, internalInstanceHandle);
362 + }
363 + }
364 + return {
365 + node: clone,
366 + canonical: instance.canonical,
367 + };
368 +}
369 +
370 +export function createContainerChildSet(container: Container): ChildSet {
371 + return createChildNodeSet(container);
372 +}
373 +
374 +export function appendChildToContainerChildSet(
375 + childSet: ChildSet,
376 + child: Instance | TextInstance,
377 +): void {
378 + appendChildNodeToSet(childSet, child.node);
379 +}
380 +
381 +export function finalizeContainerChildren(
382 + container: Container,
383 + newChildren: ChildSet,
384 +): void {
385 + completeRoot(container, newChildren);
386 +}
387
381 -export default ReactFabricHostConfig;
388 +export function replaceContainerChildren(
389 + container: Container,
390 + newChildren: ChildSet,
391 +): void {}
packages/react-native-renderer/src/ReactFabricRenderer.js deleted
-15
@@ -1,15 +0,0 @@
1 -/**
2 - * Copyright (c) 2013-present, Facebook, Inc.
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 -import ReactFiberReconciler from 'react-reconciler';
11 -import ReactFabricHostConfig from './ReactFabricHostConfig';
12 -
13 -const ReactFabricRenderer = ReactFiberReconciler(ReactFabricHostConfig);
14 -
15 -export default ReactFabricRenderer;
packages/react-native-renderer/src/ReactNativeFrameScheduling.js
+5 -2
@@ -7,7 +7,7 @@
7 * @flow
8 */
9
10 -import type {Deadline} from 'react-reconciler';
10 +import type {Deadline} from 'react-reconciler/src/ReactFiberScheduler';
11
12 const hasNativePerformanceNow =
13 typeof performance === 'object' && typeof performance.now === 'function';
@@ -43,7 +43,10 @@ function setTimeoutCallback() {
43 // RN has a poor polyfill for requestIdleCallback so we aren't using it.
44 // This implementation is only intended for short-term use anyway.
45 // We also don't implement cancel functionality b'c Fiber doesn't currently need it.
46 -function scheduleDeferredCallback(callback: Callback): number {
46 +function scheduleDeferredCallback(
47 + callback: Callback,
48 + options?: {timeout: number},
49 +): number {
50 // We assume only one callback is scheduled at a time b'c that's how Fiber works.
51 scheduledCallback = callback;
52 return setTimeout(setTimeoutCallback, 1);
packages/react-native-renderer/src/ReactNativeHostConfig.js
+357 -346
@@ -26,18 +26,22 @@ import {
26 import ReactNativeFiberHostComponent from './ReactNativeFiberHostComponent';
27 import * as ReactNativeFrameScheduling from './ReactNativeFrameScheduling';
28
29 -type Container = number;
29 +export type Type = string;
30 +export type Props = Object;
31 +export type Container = number;
32 export type Instance = {
33 _children: Array<Instance | number>,
34 _nativeTag: number,
35 viewConfig: ReactNativeBaseComponentViewConfig,
36 };
35 -type Props = Object;
36 -type TextInstance = number;
37 -
38 -type HostContext = $ReadOnly<{|
37 +export type TextInstance = number;
38 +export type HydratableInstance = Instance | TextInstance;
39 +export type PublicInstance = Instance;
40 +export type HostContext = $ReadOnly<{|
41 isInAParentText: boolean,
42 |}>;
43 +export type UpdatePayload = Object; // Unused
44 +export type ChildSet = void; // Unused
45
46 // Counter for uniquely identifying views.
47 // % 10 === 1 means it is a rootTag.
@@ -63,369 +67,376 @@ function recursivelyUncacheFiberNode(node: Instance | TextInstance) {
67 }
68 }
69
66 -const ReactNativeHostConfig = {
67 - appendInitialChild(
68 - parentInstance: Instance,
69 - child: Instance | TextInstance,
70 - ): void {
71 - parentInstance._children.push(child);
72 - },
73 -
74 - createInstance(
75 - type: string,
76 - props: Props,
77 - rootContainerInstance: Container,
78 - hostContext: HostContext,
79 - internalInstanceHandle: Object,
80 - ): Instance {
81 - const tag = allocateTag();
82 - const viewConfig = ReactNativeViewConfigRegistry.get(type);
83 -
84 - if (__DEV__) {
85 - for (const key in viewConfig.validAttributes) {
86 - if (props.hasOwnProperty(key)) {
87 - deepFreezeAndThrowOnMutationInDev(props[key]);
88 - }
70 +export * from 'shared/HostConfigWithNoPersistence';
71 +export * from 'shared/HostConfigWithNoHydration';
72 +
73 +export function appendInitialChild(
74 + parentInstance: Instance,
75 + child: Instance | TextInstance,
76 +): void {
77 + parentInstance._children.push(child);
78 +}
79 +
80 +export function createInstance(
81 + type: string,
82 + props: Props,
83 + rootContainerInstance: Container,
84 + hostContext: HostContext,
85 + internalInstanceHandle: Object,
86 +): Instance {
87 + const tag = allocateTag();
88 + const viewConfig = ReactNativeViewConfigRegistry.get(type);
89 +
90 + if (__DEV__) {
91 + for (const key in viewConfig.validAttributes) {
92 + if (props.hasOwnProperty(key)) {
93 + deepFreezeAndThrowOnMutationInDev(props[key]);
94 }
95 }
96 + }
97
92 - invariant(
93 - type !== 'RCTView' || !hostContext.isInAParentText,
94 - 'Nesting of <View> within <Text> is not currently supported.',
95 - );
98 + invariant(
99 + type !== 'RCTView' || !hostContext.isInAParentText,
100 + 'Nesting of <View> within <Text> is not currently supported.',
101 + );
102
97 - const updatePayload = ReactNativeAttributePayload.create(
98 - props,
99 - viewConfig.validAttributes,
100 - );
103 + const updatePayload = ReactNativeAttributePayload.create(
104 + props,
105 + viewConfig.validAttributes,
106 + );
107
102 - UIManager.createView(
103 - tag, // reactTag
104 - viewConfig.uiViewClassName, // viewName
105 - rootContainerInstance, // rootTag
106 - updatePayload, // props
107 - );
108 + UIManager.createView(
109 + tag, // reactTag
110 + viewConfig.uiViewClassName, // viewName
111 + rootContainerInstance, // rootTag
112 + updatePayload, // props
113 + );
114
109 - const component = new ReactNativeFiberHostComponent(tag, viewConfig);
110 -
111 - precacheFiberNode(internalInstanceHandle, tag);
112 - updateFiberProps(tag, props);
113 -
114 - // Not sure how to avoid this cast. Flow is okay if the component is defined
115 - // in the same file but if it's external it can't see the types.
116 - return ((component: any): Instance);
117 - },
118 -
119 - createTextInstance(
120 - text: string,
121 - rootContainerInstance: Container,
122 - hostContext: HostContext,
123 - internalInstanceHandle: Object,
124 - ): TextInstance {
125 - invariant(
126 - hostContext.isInAParentText,
127 - 'Text strings must be rendered within a <Text> component.',
128 - );
115 + const component = new ReactNativeFiberHostComponent(tag, viewConfig);
116
130 - const tag = allocateTag();
117 + precacheFiberNode(internalInstanceHandle, tag);
118 + updateFiberProps(tag, props);
119
132 - UIManager.createView(
133 - tag, // reactTag
134 - 'RCTRawText', // viewName
135 - rootContainerInstance, // rootTag
136 - {text: text}, // props
137 - );
120 + // Not sure how to avoid this cast. Flow is okay if the component is defined
121 + // in the same file but if it's external it can't see the types.
122 + return ((component: any): Instance);
123 +}
124
139 - precacheFiberNode(internalInstanceHandle, tag);
125 +export function createTextInstance(
126 + text: string,
127 + rootContainerInstance: Container,
128 + hostContext: HostContext,
129 + internalInstanceHandle: Object,
130 +): TextInstance {
131 + invariant(
132 + hostContext.isInAParentText,
133 + 'Text strings must be rendered within a <Text> component.',
134 + );
135
141 - return tag;
142 - },
136 + const tag = allocateTag();
137
144 - finalizeInitialChildren(
145 - parentInstance: Instance,
146 - type: string,
147 - props: Props,
148 - rootContainerInstance: Container,
149 - ): boolean {
150 - // Don't send a no-op message over the bridge.
151 - if (parentInstance._children.length === 0) {
152 - return false;
153 - }
138 + UIManager.createView(
139 + tag, // reactTag
140 + 'RCTRawText', // viewName
141 + rootContainerInstance, // rootTag
142 + {text: text}, // props
143 + );
144
155 - // Map from child objects to native tags.
156 - // Either way we need to pass a copy of the Array to prevent it from being frozen.
157 - const nativeTags = parentInstance._children.map(
158 - child =>
159 - typeof child === 'number'
160 - ? child // Leaf node (eg text)
161 - : child._nativeTag,
162 - );
145 + precacheFiberNode(internalInstanceHandle, tag);
146 +
147 + return tag;
148 +}
149 +
150 +export function finalizeInitialChildren(
151 + parentInstance: Instance,
152 + type: string,
153 + props: Props,
154 + rootContainerInstance: Container,
155 + hostContext: HostContext,
156 +): boolean {
157 + // Don't send a no-op message over the bridge.
158 + if (parentInstance._children.length === 0) {
159 + return false;
160 + }
161 +
162 + // Map from child objects to native tags.
163 + // Either way we need to pass a copy of the Array to prevent it from being frozen.
164 + const nativeTags = parentInstance._children.map(
165 + child =>
166 + typeof child === 'number'
167 + ? child // Leaf node (eg text)
168 + : child._nativeTag,
169 + );
170 +
171 + UIManager.setChildren(
172 + parentInstance._nativeTag, // containerTag
173 + nativeTags, // reactTags
174 + );
175 +
176 + return false;
177 +}
178 +
179 +export function getRootHostContext(
180 + rootContainerInstance: Container,
181 +): HostContext {
182 + return {isInAParentText: false};
183 +}
184 +
185 +export function getChildHostContext(
186 + parentHostContext: HostContext,
187 + type: string,
188 + rootContainerInstance: Container,
189 +): HostContext {
190 + const prevIsInAParentText = parentHostContext.isInAParentText;
191 + const isInAParentText =
192 + type === 'AndroidTextInput' || // Android
193 + type === 'RCTMultilineTextInputView' || // iOS
194 + type === 'RCTSinglelineTextInputView' || // iOS
195 + type === 'RCTText' ||
196 + type === 'RCTVirtualText';
197 +
198 + if (prevIsInAParentText !== isInAParentText) {
199 + return {isInAParentText};
200 + } else {
201 + return parentHostContext;
202 + }
203 +}
204 +
205 +export function getPublicInstance(instance: Instance): * {
206 + return instance;
207 +}
208 +
209 +export function prepareForCommit(containerInfo: Container): void {
210 + // Noop
211 +}
212 +
213 +export function prepareUpdate(
214 + instance: Instance,
215 + type: string,
216 + oldProps: Props,
217 + newProps: Props,
218 + rootContainerInstance: Container,
219 + hostContext: HostContext,
220 +): null | Object {
221 + return emptyObject;
222 +}
223 +
224 +export function resetAfterCommit(containerInfo: Container): void {
225 + // Noop
226 +}
227 +
228 +export const now = ReactNativeFrameScheduling.now;
229 +export const isPrimaryRenderer = true;
230 +export const scheduleDeferredCallback =
231 + ReactNativeFrameScheduling.scheduleDeferredCallback;
232 +export const cancelDeferredCallback =
233 + ReactNativeFrameScheduling.cancelDeferredCallback;
234
164 - UIManager.setChildren(
235 +export function shouldDeprioritizeSubtree(type: string, props: Props): boolean {
236 + return false;
237 +}
238 +
239 +export function shouldSetTextContent(type: string, props: Props): boolean {
240 + // TODO (bvaughn) Revisit this decision.
241 + // Always returning false simplifies the createInstance() implementation,
242 + // But creates an additional child Fiber for raw text children.
243 + // No additional native views are created though.
244 + // It's not clear to me which is better so I'm deferring for now.
245 + // More context @ github.com/facebook/react/pull/8560#discussion_r92111303
246 + return false;
247 +}
248 +
249 +// -------------------
250 +// Mutation
251 +// -------------------
252 +
253 +export const supportsMutation = true;
254 +
255 +export function appendChild(
256 + parentInstance: Instance,
257 + child: Instance | TextInstance,
258 +): void {
259 + const childTag = typeof child === 'number' ? child : child._nativeTag;
260 + const children = parentInstance._children;
261 + const index = children.indexOf(child);
262 +
263 + if (index >= 0) {
264 + children.splice(index, 1);
265 + children.push(child);
266 +
267 + UIManager.manageChildren(
268 parentInstance._nativeTag, // containerTag
166 - nativeTags, // reactTags
269 + [index], // moveFromIndices
270 + [children.length - 1], // moveToIndices
271 + [], // addChildReactTags
272 + [], // addAtIndices
273 + [], // removeAtIndices
274 );
275 + } else {
276 + children.push(child);
277
169 - return false;
170 - },
171 -
172 - getRootHostContext(rootContainerInstance: Container): HostContext {
173 - return {isInAParentText: false};
174 - },
175 -
176 - getChildHostContext(
177 - parentHostContext: HostContext,
178 - type: string,
179 - ): HostContext {
180 - const prevIsInAParentText = parentHostContext.isInAParentText;
181 - const isInAParentText =
182 - type === 'AndroidTextInput' || // Android
183 - type === 'RCTMultilineTextInputView' || // iOS
184 - type === 'RCTSinglelineTextInputView' || // iOS
185 - type === 'RCTText' ||
186 - type === 'RCTVirtualText';
187 -
188 - if (prevIsInAParentText !== isInAParentText) {
189 - return {isInAParentText};
190 - } else {
191 - return parentHostContext;
192 - }
193 - },
278 + UIManager.manageChildren(
279 + parentInstance._nativeTag, // containerTag
280 + [], // moveFromIndices
281 + [], // moveToIndices
282 + [childTag], // addChildReactTags
283 + [children.length - 1], // addAtIndices
284 + [], // removeAtIndices
285 + );
286 + }
287 +}
288
195 - getPublicInstance(instance: Instance): * {
196 - return instance;
197 - },
289 +export function appendChildToContainer(
290 + parentInstance: Container,
291 + child: Instance | TextInstance,
292 +): void {
293 + const childTag = typeof child === 'number' ? child : child._nativeTag;
294 + UIManager.setChildren(
295 + parentInstance, // containerTag
296 + [childTag], // reactTags
297 + );
298 +}
299
199 - now: ReactNativeFrameScheduling.now,
300 +export function commitTextUpdate(
301 + textInstance: TextInstance,
302 + oldText: string,
303 + newText: string,
304 +): void {
305 + UIManager.updateView(
306 + textInstance, // reactTag
307 + 'RCTRawText', // viewName
308 + {text: newText}, // props
309 + );
310 +}
311
201 - isPrimaryRenderer: true,
312 +export function commitMount(
313 + instance: Instance,
314 + type: string,
315 + newProps: Props,
316 + internalInstanceHandle: Object,
317 +): void {
318 + // Noop
319 +}
320
203 - prepareForCommit(): void {
204 - // Noop
205 - },
321 +export function commitUpdate(
322 + instance: Instance,
323 + updatePayloadTODO: Object,
324 + type: string,
325 + oldProps: Props,
326 + newProps: Props,
327 + internalInstanceHandle: Object,
328 +): void {
329 + const viewConfig = instance.viewConfig;
330 +
331 + updateFiberProps(instance._nativeTag, newProps);
332 +
333 + const updatePayload = ReactNativeAttributePayload.diff(
334 + oldProps,
335 + newProps,
336 + viewConfig.validAttributes,
337 + );
338 +
339 + // Avoid the overhead of bridge calls if there's no update.
340 + // This is an expensive no-op for Android, and causes an unnecessary
341 + // view invalidation for certain components (eg RCTTextInput) on iOS.
342 + if (updatePayload != null) {
343 + UIManager.updateView(
344 + instance._nativeTag, // reactTag
345 + viewConfig.uiViewClassName, // viewName
346 + updatePayload, // props
347 + );
348 + }
349 +}
350
207 - prepareUpdate(
208 - instance: Instance,
209 - type: string,
210 - oldProps: Props,
211 - newProps: Props,
212 - rootContainerInstance: Container,
213 - hostContext: HostContext,
214 - ): null | Object {
215 - return emptyObject;
216 - },
351 +export function insertBefore(
352 + parentInstance: Instance,
353 + child: Instance | TextInstance,
354 + beforeChild: Instance | TextInstance,
355 +): void {
356 + const children = (parentInstance: any)._children;
357 + const index = children.indexOf(child);
358 +
359 + // Move existing child or add new child?
360 + if (index >= 0) {
361 + children.splice(index, 1);
362 + const beforeChildIndex = children.indexOf(beforeChild);
363 + children.splice(beforeChildIndex, 0, child);
364 +
365 + UIManager.manageChildren(
366 + (parentInstance: any)._nativeTag, // containerID
367 + [index], // moveFromIndices
368 + [beforeChildIndex], // moveToIndices
369 + [], // addChildReactTags
370 + [], // addAtIndices
371 + [], // removeAtIndices
372 + );
373 + } else {
374 + const beforeChildIndex = children.indexOf(beforeChild);
375 + children.splice(beforeChildIndex, 0, child);
376 +
377 + const childTag = typeof child === 'number' ? child : child._nativeTag;
378 +
379 + UIManager.manageChildren(
380 + (parentInstance: any)._nativeTag, // containerID
381 + [], // moveFromIndices
382 + [], // moveToIndices
383 + [childTag], // addChildReactTags
384 + [beforeChildIndex], // addAtIndices
385 + [], // removeAtIndices
386 + );
387 + }
388 +}
389
218 - resetAfterCommit(): void {
219 - // Noop
220 - },
390 +export function insertInContainerBefore(
391 + parentInstance: Container,
392 + child: Instance | TextInstance,
393 + beforeChild: Instance | TextInstance,
394 +): void {
395 + // TODO (bvaughn): Remove this check when...
396 + // We create a wrapper object for the container in ReactNative render()
397 + // Or we refactor to remove wrapper objects entirely.
398 + // For more info on pros/cons see PR #8560 description.
399 + invariant(
400 + typeof parentInstance !== 'number',
401 + 'Container does not support insertBefore operation',
402 + );
403 +}
404
222 - scheduleDeferredCallback: ReactNativeFrameScheduling.scheduleDeferredCallback,
223 - cancelDeferredCallback: ReactNativeFrameScheduling.cancelDeferredCallback,
405 +export function removeChild(
406 + parentInstance: Instance,
407 + child: Instance | TextInstance,
408 +): void {
409 + recursivelyUncacheFiberNode(child);
410 + const children = parentInstance._children;
411 + const index = children.indexOf(child);
412 +
413 + children.splice(index, 1);
414 +
415 + UIManager.manageChildren(
416 + parentInstance._nativeTag, // containerID
417 + [], // moveFromIndices
418 + [], // moveToIndices
419 + [], // addChildReactTags
420 + [], // addAtIndices
421 + [index], // removeAtIndices
422 + );
423 +}
424
225 - shouldDeprioritizeSubtree(type: string, props: Props): boolean {
226 - return false;
227 - },
228 -
229 - shouldSetTextContent(type: string, props: Props): boolean {
230 - // TODO (bvaughn) Revisit this decision.
231 - // Always returning false simplifies the createInstance() implementation,
232 - // But creates an additional child Fiber for raw text children.
233 - // No additional native views are created though.
234 - // It's not clear to me which is better so I'm deferring for now.
235 - // More context @ github.com/facebook/react/pull/8560#discussion_r92111303
236 - return false;
237 - },
238 -
239 - mutation: {
240 - appendChild(
241 - parentInstance: Instance,
242 - child: Instance | TextInstance,
243 - ): void {
244 - const childTag = typeof child === 'number' ? child : child._nativeTag;
245 - const children = parentInstance._children;
246 - const index = children.indexOf(child);
247 -
248 - if (index >= 0) {
249 - children.splice(index, 1);
250 - children.push(child);
251 -
252 - UIManager.manageChildren(
253 - parentInstance._nativeTag, // containerTag
254 - [index], // moveFromIndices
255 - [children.length - 1], // moveToIndices
256 - [], // addChildReactTags
257 - [], // addAtIndices
258 - [], // removeAtIndices
259 - );
260 - } else {
261 - children.push(child);
262 -
263 - UIManager.manageChildren(
264 - parentInstance._nativeTag, // containerTag
265 - [], // moveFromIndices
266 - [], // moveToIndices
267 - [childTag], // addChildReactTags
268 - [children.length - 1], // addAtIndices
269 - [], // removeAtIndices
270 - );
271 - }
272 - },
273 -
274 - appendChildToContainer(
275 - parentInstance: Container,
276 - child: Instance | TextInstance,
277 - ): void {
278 - const childTag = typeof child === 'number' ? child : child._nativeTag;
279 - UIManager.setChildren(
280 - parentInstance, // containerTag
281 - [childTag], // reactTags
282 - );
283 - },
284 -
285 - commitTextUpdate(
286 - textInstance: TextInstance,
287 - oldText: string,
288 - newText: string,
289 - ): void {
290 - UIManager.updateView(
291 - textInstance, // reactTag
292 - 'RCTRawText', // viewName
293 - {text: newText}, // props
294 - );
295 - },
296 -
297 - commitMount(
298 - instance: Instance,
299 - type: string,
300 - newProps: Props,
301 - internalInstanceHandle: Object,
302 - ): void {
303 - // Noop
304 - },
305 -
306 - commitUpdate(
307 - instance: Instance,
308 - updatePayloadTODO: Object,
309 - type: string,
310 - oldProps: Props,
311 - newProps: Props,
312 - internalInstanceHandle: Object,
313 - ): void {
314 - const viewConfig = instance.viewConfig;
315 -
316 - updateFiberProps(instance._nativeTag, newProps);
317 -
318 - const updatePayload = ReactNativeAttributePayload.diff(
319 - oldProps,
320 - newProps,
321 - viewConfig.validAttributes,
322 - );
323 -
324 - // Avoid the overhead of bridge calls if there's no update.
325 - // This is an expensive no-op for Android, and causes an unnecessary
326 - // view invalidation for certain components (eg RCTTextInput) on iOS.
327 - if (updatePayload != null) {
328 - UIManager.updateView(
329 - instance._nativeTag, // reactTag
330 - viewConfig.uiViewClassName, // viewName
331 - updatePayload, // props
332 - );
333 - }
334 - },
335 -
336 - insertBefore(
337 - parentInstance: Instance,
338 - child: Instance | TextInstance,
339 - beforeChild: Instance | TextInstance,
340 - ): void {
341 - const children = (parentInstance: any)._children;
342 - const index = children.indexOf(child);
343 -
344 - // Move existing child or add new child?
345 - if (index >= 0) {
346 - children.splice(index, 1);
347 - const beforeChildIndex = children.indexOf(beforeChild);
348 - children.splice(beforeChildIndex, 0, child);
349 -
350 - UIManager.manageChildren(
351 - (parentInstance: any)._nativeTag, // containerID
352 - [index], // moveFromIndices
353 - [beforeChildIndex], // moveToIndices
354 - [], // addChildReactTags
355 - [], // addAtIndices
356 - [], // removeAtIndices
357 - );
358 - } else {
359 - const beforeChildIndex = children.indexOf(beforeChild);
360 - children.splice(beforeChildIndex, 0, child);
361 -
362 - const childTag = typeof child === 'number' ? child : child._nativeTag;
363 -
364 - UIManager.manageChildren(
365 - (parentInstance: any)._nativeTag, // containerID
366 - [], // moveFromIndices
367 - [], // moveToIndices
368 - [childTag], // addChildReactTags
369 - [beforeChildIndex], // addAtIndices
370 - [], // removeAtIndices
371 - );
372 - }
373 - },
374 -
375 - insertInContainerBefore(
376 - parentInstance: Container,
377 - child: Instance | TextInstance,
378 - beforeChild: Instance | TextInstance,
379 - ): void {
380 - // TODO (bvaughn): Remove this check when...
381 - // We create a wrapper object for the container in ReactNative render()
382 - // Or we refactor to remove wrapper objects entirely.
383 - // For more info on pros/cons see PR #8560 description.
384 - invariant(
385 - typeof parentInstance !== 'number',
386 - 'Container does not support insertBefore operation',
387 - );
388 - },
389 -
390 - removeChild(
391 - parentInstance: Instance,
392 - child: Instance | TextInstance,
393 - ): void {
394 - recursivelyUncacheFiberNode(child);
395 - const children = parentInstance._children;
396 - const index = children.indexOf(child);
397 -
398 - children.splice(index, 1);
399 -
400 - UIManager.manageChildren(
401 - parentInstance._nativeTag, // containerID
402 - [], // moveFromIndices
403 - [], // moveToIndices
404 - [], // addChildReactTags
405 - [], // addAtIndices
406 - [index], // removeAtIndices
407 - );
408 - },
409 -
410 - removeChildFromContainer(
411 - parentInstance: Container,
412 - child: Instance | TextInstance,
413 - ): void {
414 - recursivelyUncacheFiberNode(child);
415 - UIManager.manageChildren(
416 - parentInstance, // containerID
417 - [], // moveFromIndices
418 - [], // moveToIndices
419 - [], // addChildReactTags
420 - [], // addAtIndices
421 - [0], // removeAtIndices
422 - );
423 - },
424 -
425 - resetTextContent(instance: Instance): void {
426 - // Noop
427 - },
428 - },
429 -};
425 +export function removeChildFromContainer(
426 + parentInstance: Container,
427 + child: Instance | TextInstance,
428 +): void {
429 + recursivelyUncacheFiberNode(child);
430 + UIManager.manageChildren(
431 + parentInstance, // containerID
432 + [], // moveFromIndices
433 + [], // moveToIndices
434 + [], // addChildReactTags
435 + [], // addAtIndices
436 + [0], // removeAtIndices
437 + );
438 +}
439
431 -export default ReactNativeHostConfig;
440 +export function resetTextContent(instance: Instance): void {
441 + // Noop
442 +}
packages/react-native-renderer/src/ReactNativeRenderer.js
+2 -5
@@ -12,7 +12,7 @@ import type {ReactNodeList} from 'shared/ReactTypes';
12
13 import './ReactNativeInjection';
14
15 -import ReactFiberReconciler from 'react-reconciler';
15 +import * as ReactNativeFiberRenderer from 'react-reconciler/inline.native';
16 import * as ReactPortal from 'shared/ReactPortal';
17 import * as ReactGenericBatching from 'events/ReactGenericBatching';
18 import ReactVersion from 'shared/ReactVersion';
@@ -21,7 +21,6 @@ import UIManager from 'UIManager';
21
22 import {getStackAddendumByWorkInProgressFiber} from 'shared/ReactFiberComponentTreeHook';
23
24 -import ReactNativeHostConfig from './ReactNativeHostConfig';
24 import NativeMethodsMixin from './NativeMethodsMixin';
25 import ReactNativeComponent from './ReactNativeComponent';
26 import * as ReactNativeComponentTree from './ReactNativeComponentTree';
@@ -31,8 +30,6 @@ import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
30 import getComponentName from 'shared/getComponentName';
31 import warning from 'fbjs/lib/warning';
32
34 -const ReactNativeFiberRenderer = ReactFiberReconciler(ReactNativeHostConfig);
35 -
33 const findHostInstance = ReactNativeFiberRenderer.findHostInstance;
34
35 function findNodeHandle(componentOrHandle: any): ?number {
@@ -71,7 +68,7 @@ function findNodeHandle(componentOrHandle: any): ?number {
68 }
69 if (hostInstance.canonical) {
70 // Fabric
74 - return hostInstance.canonical._nativeTag;
71 + return (hostInstance.canonical: any)._nativeTag;
72 }
73 return hostInstance._nativeTag;
74 }
packages/react-noop-renderer/src/ReactNoop.js
+2
@@ -14,9 +14,11 @@
14 * environment.
15 */
16
17 +import ReactFiberReconciler from 'react-reconciler';
18 import createReactNoop from './createReactNoop';
19
20 const ReactNoop = createReactNoop(
21 + ReactFiberReconciler, // reconciler
22 true, // useMutation
23 );
24
packages/react-noop-renderer/src/ReactNoopPersistent.js
+2
@@ -14,9 +14,11 @@
14 * environment.
15 */
16
17 +import ReactFiberPersistentReconciler from 'react-reconciler/persistent';
18 import createReactNoop from './createReactNoop';
19
20 const ReactNoopPersistent = createReactNoop(
21 + ReactFiberPersistentReconciler, // reconciler
22 false, // useMutation
23 );
24
packages/react-noop-renderer/src/createReactNoop.js
+88 -85
@@ -18,7 +18,6 @@ import type {Fiber} from 'react-reconciler/src/ReactFiber';
18 import type {UpdateQueue} from 'react-reconciler/src/ReactUpdateQueue';
19 import type {ReactNodeList} from 'shared/ReactTypes';
20
21 -import ReactFiberReconciler from 'react-reconciler';
21 import * as ReactPortal from 'shared/ReactPortal';
22 import emptyObject from 'fbjs/lib/emptyObject';
23 import expect from 'expect';
@@ -33,7 +32,7 @@ type Instance = {|
32 |};
33 type TextInstance = {|text: string, id: number|};
34
36 -function createReactNoop(useMutation: boolean) {
35 +function createReactNoop(reconciler: Function, useMutation: boolean) {
36 const UPDATE_SIGNAL = {};
37 let scheduledCallback = null;
38
@@ -187,101 +186,105 @@ function createReactNoop(useMutation: boolean) {
186 },
187
188 isPrimaryRenderer: true,
189 + supportsHydration: false,
190 };
191
192 const hostConfig = useMutation
193 ? {
194 ...sharedHostConfig,
195 - mutation: {
196 - commitMount(instance: Instance, type: string, newProps: Props): void {
197 - // Noop
198 - },
199 -
200 - commitUpdate(
201 - instance: Instance,
202 - updatePayload: Object,
203 - type: string,
204 - oldProps: Props,
205 - newProps: Props,
206 - ): void {
207 - if (oldProps === null) {
208 - throw new Error('Should have old props');
209 - }
210 - instance.prop = newProps.prop;
211 - },
212 -
213 - commitTextUpdate(
214 - textInstance: TextInstance,
215 - oldText: string,
216 - newText: string,
217 - ): void {
218 - textInstance.text = newText;
219 - },
220 -
221 - appendChild: appendChild,
222 - appendChildToContainer: appendChild,
223 - insertBefore: insertBefore,
224 - insertInContainerBefore: insertBefore,
225 - removeChild: removeChild,
226 - removeChildFromContainer: removeChild,
227 -
228 - resetTextContent(instance: Instance): void {},
195 +
196 + supportsMutation: true,
197 + supportsPersistence: false,
198 +
199 + commitMount(instance: Instance, type: string, newProps: Props): void {
200 + // Noop
201 + },
202 +
203 + commitUpdate(
204 + instance: Instance,
205 + updatePayload: Object,
206 + type: string,
207 + oldProps: Props,
208 + newProps: Props,
209 + ): void {
210 + if (oldProps === null) {
211 + throw new Error('Should have old props');
212 + }
213 + instance.prop = newProps.prop;
214 + },
215 +
216 + commitTextUpdate(
217 + textInstance: TextInstance,
218 + oldText: string,
219 + newText: string,
220 + ): void {
221 + textInstance.text = newText;
222 },
223 +
224 + appendChild: appendChild,
225 + appendChildToContainer: appendChild,
226 + insertBefore: insertBefore,
227 + insertInContainerBefore: insertBefore,
228 + removeChild: removeChild,
229 + removeChildFromContainer: removeChild,
230 +
231 + resetTextContent(instance: Instance): void {},
232 }
233 : {
234 ...sharedHostConfig,
233 - persistence: {
234 - cloneInstance(
235 - instance: Instance,
236 - updatePayload: null | Object,
237 - type: string,
238 - oldProps: Props,
239 - newProps: Props,
240 - internalInstanceHandle: Object,
241 - keepChildren: boolean,
242 - recyclableInstance: null | Instance,
243 - ): Instance {
244 - const clone = {
245 - id: instance.id,
246 - type: type,
247 - children: keepChildren ? instance.children : [],
248 - prop: newProps.prop,
249 - };
250 - Object.defineProperty(clone, 'id', {
251 - value: clone.id,
252 - enumerable: false,
253 - });
254 - return clone;
255 - },
256 -
257 - createContainerChildSet(
258 - container: Container,
259 - ): Array<Instance | TextInstance> {
260 - return [];
261 - },
262 -
263 - appendChildToContainerChildSet(
264 - childSet: Array<Instance | TextInstance>,
265 - child: Instance | TextInstance,
266 - ): void {
267 - childSet.push(child);
268 - },
269 -
270 - finalizeContainerChildren(
271 - container: Container,
272 - newChildren: Array<Instance | TextInstance>,
273 - ): void {},
274 -
275 - replaceContainerChildren(
276 - container: Container,
277 - newChildren: Array<Instance | TextInstance>,
278 - ): void {
279 - container.children = newChildren;
280 - },
235 + supportsMutation: false,
236 + supportsPersistence: true,
237 +
238 + cloneInstance(
239 + instance: Instance,
240 + updatePayload: null | Object,
241 + type: string,
242 + oldProps: Props,
243 + newProps: Props,
244 + internalInstanceHandle: Object,
245 + keepChildren: boolean,
246 + recyclableInstance: null | Instance,
247 + ): Instance {
248 + const clone = {
249 + id: instance.id,
250 + type: type,
251 + children: keepChildren ? instance.children : [],
252 + prop: newProps.prop,
253 + };
254 + Object.defineProperty(clone, 'id', {
255 + value: clone.id,
256 + enumerable: false,
257 + });
258 + return clone;
259 + },
260 +
261 + createContainerChildSet(
262 + container: Container,
263 + ): Array<Instance | TextInstance> {
264 + return [];
265 + },
266 +
267 + appendChildToContainerChildSet(
268 + childSet: Array<Instance | TextInstance>,
269 + child: Instance | TextInstance,
270 + ): void {
271 + childSet.push(child);
272 + },
273 +
274 + finalizeContainerChildren(
275 + container: Container,
276 + newChildren: Array<Instance | TextInstance>,
277 + ): void {},
278 +
279 + replaceContainerChildren(
280 + container: Container,
281 + newChildren: Array<Instance | TextInstance>,
282 + ): void {
283 + container.children = newChildren;
284 },
285 };
286
284 - const NoopRenderer = ReactFiberReconciler(hostConfig);
287 + const NoopRenderer = reconciler(hostConfig);
288
289 const rootContainers = new Map();
290 const roots = new Map();
packages/react-reconciler/index.js
+12 -9
@@ -3,18 +3,21 @@
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
6 */
7
10 -'use strict';
8 +// This entry point is intentionally not typed. It exists only for third-party
9 +// renderers. The renderers we ship (such as React DOM) instead import a named
10 +// "inline" entry point (for example, `react-reconciler/inline.dom`). It uses
11 +// the same code, but the Flow configuration redirects the host config to its
12 +// real implementation so we can check it against exact intended host types.
13 +//
14 +// Only one renderer (the one you passed to `yarn flow <renderer>`) is fully
15 +// type-checked at any given time. The Flow config maps the
16 +// `react-reconciler/inline.<renderer>` import (which is *not* Flow typed) to
17 +// `react-reconciler/inline-typed` (which *is*) for the current renderer.
18 +// On CI, we run Flow checks for each renderer separately.
19
12 -// TODO: bundle Flow types with the package.
13 -export type {
14 - HostConfig,
15 - Deadline,
16 - Reconciler,
17 -} from './src/ReactFiberReconciler';
20 +'use strict';
21
22 const ReactFiberReconciler = require('./src/ReactFiberReconciler');
23
packages/react-reconciler/inline-typed.js new
+24
@@ -0,0 +1,24 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
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 +// This file must have the Flow annotation.
11 +//
12 +// This is the Flow-typed entry point for the reconciler. It should not be
13 +// imported directly in code. Instead, our Flow configuration uses this entry
14 +// point for the currently checked renderer (the one you passed to `yarn flow`).
15 +//
16 +// For example, if you run `yarn flow dom`, `react-reconciler/inline.dom` points
17 +// to this module (and thus will be considered Flow-typed). But other renderers
18 +// (e.g. `react-test-renderer`) will see reconciler as untyped during the check.
19 +//
20 +// We can't make all entry points typed at the same time because different
21 +// renderers have different host config types. So we check them one by one.
22 +// We run Flow on all renderers on CI.
23 +
24 +export * from './src/ReactFiberReconciler';
packages/react-reconciler/inline.art.js new
+11
@@ -0,0 +1,11 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
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 +
8 +// This file intentionally does *not* have the Flow annotation.
9 +// Don't add it. See `./inline-typed.js` for an explanation.
10 +
11 +export * from './src/ReactFiberReconciler';
packages/react-reconciler/inline.dom.js new
+11
@@ -0,0 +1,11 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
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 +
8 +// This file intentionally does *not* have the Flow annotation.
9 +// Don't add it. See `./inline-typed.js` for an explanation.
10 +
11 +export * from './src/ReactFiberReconciler';
packages/react-reconciler/inline.fabric.js new
+11
@@ -0,0 +1,11 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
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 +
8 +// This file intentionally does *not* have the Flow annotation.
9 +// Don't add it. See `./inline-typed.js` for an explanation.
10 +
11 +export * from './src/ReactFiberReconciler';
packages/react-reconciler/inline.native.js new
+11
@@ -0,0 +1,11 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
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 +
8 +// This file intentionally does *not* have the Flow annotation.
9 +// Don't add it. See `./inline-typed.js` for an explanation.
10 +
11 +export * from './src/ReactFiberReconciler';
packages/react-reconciler/inline.test.js new
+11
@@ -0,0 +1,11 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
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 +
8 +// This file intentionally does *not* have the Flow annotation.
9 +// Don't add it. See `./inline-typed.js` for an explanation.
10 +
11 +export * from './src/ReactFiberReconciler';
packages/react-reconciler/persistent.js
-2
@@ -3,8 +3,6 @@
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
6 */
7
8 'use strict';
packages/react-reconciler/src/ReactFiberBeginWork.js
+931 -993
@@ -7,16 +7,10 @@
7 * @flow
8 */
9
10 -import type {HostConfig} from 'react-reconciler';
10 import type {ReactProviderType, ReactContext} from 'shared/ReactTypes';
11 import type {Fiber} from 'react-reconciler/src/ReactFiber';
13 -import type {HostContext} from './ReactFiberHostContext';
14 -import type {LegacyContext} from './ReactFiberContext';
15 -import type {NewContext} from './ReactFiberNewContext';
16 -import type {HydrationContext} from './ReactFiberHydrationContext';
12 import type {FiberRoot} from './ReactFiberRoot';
13 import type {ExpirationTime} from './ReactFiberExpirationTime';
19 -import type {ProfilerTimer} from './ReactProfilerTimer';
14 import checkPropTypes from 'prop-types/checkPropTypes';
15
16 import {
@@ -58,9 +52,7 @@ import warning from 'fbjs/lib/warning';
52 import ReactDebugCurrentFiber from './ReactDebugCurrentFiber';
53 import {cancelWorkTimer} from './ReactDebugFiberPerf';
54
61 -import ReactFiberClassComponent, {
62 - applyDerivedStateFromProps,
63 -} from './ReactFiberClassComponent';
55 +import {applyDerivedStateFromProps} from './ReactFiberClassComponent';
56 import {
57 mountChildFibers,
58 reconcileChildFibers,
@@ -69,6 +61,40 @@ import {
61 import {processUpdateQueue} from './ReactUpdateQueue';
62 import {NoWork, Never} from './ReactFiberExpirationTime';
63 import {AsyncMode, StrictMode} from './ReactTypeOfMode';
64 +import {
65 + shouldSetTextContent,
66 + shouldDeprioritizeSubtree,
67 +} from './ReactFiberHostConfig';
68 +import {pushHostContext, pushHostContainer} from './ReactFiberHostContext';
69 +import {
70 + pushProvider,
71 + getContextCurrentValue,
72 + getContextChangedBits,
73 +} from './ReactFiberNewContext';
74 +import {
75 + markActualRenderTimeStarted,
76 + stopBaseRenderTimerIfRunning,
77 +} from './ReactProfilerTimer';
78 +import {
79 + getMaskedContext,
80 + getUnmaskedContext,
81 + hasContextChanged as hasLegacyContextChanged,
82 + pushContextProvider as pushLegacyContextProvider,
83 + pushTopLevelContextObject,
84 + invalidateContextProvider,
85 +} from './ReactFiberContext';
86 +import {
87 + enterHydrationState,
88 + resetHydrationState,
89 + tryToClaimNextHydratableInstance,
90 +} from './ReactFiberHydrationContext';
91 +import {
92 + adoptClassInstance,
93 + constructClassInstance,
94 + mountClassInstance,
95 + resumeMountClassInstance,
96 + updateClassInstance,
97 +} from './ReactFiberClassComponent';
98 import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt';
99
100 const {getCurrentFiberStackAddendum} = ReactDebugCurrentFiber;
@@ -83,869 +109,812 @@ if (__DEV__) {
109 didWarnAboutStatelessRefs = {};
110 }
111
86 -export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
87 - config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
88 - hostContext: HostContext<C, CX>,
89 - legacyContext: LegacyContext,
90 - newContext: NewContext,
91 - hydrationContext: HydrationContext<C, CX>,
92 - scheduleWork: (fiber: Fiber, expirationTime: ExpirationTime) => void,
93 - computeExpirationForFiber: (
94 - startTime: ExpirationTime,
95 - fiber: Fiber,
96 - ) => ExpirationTime,
97 - profilerTimer: ProfilerTimer,
98 - recalculateCurrentTime: () => ExpirationTime,
99 -) {
100 - const {shouldSetTextContent, shouldDeprioritizeSubtree} = config;
101 -
102 - const {pushHostContext, pushHostContainer} = hostContext;
103 -
104 - const {
105 - pushProvider,
106 - getContextCurrentValue,
107 - getContextChangedBits,
108 - } = newContext;
109 -
110 - const {
111 - markActualRenderTimeStarted,
112 - stopBaseRenderTimerIfRunning,
113 - } = profilerTimer;
114 -
115 - const {
116 - getMaskedContext,
117 - getUnmaskedContext,
118 - hasContextChanged: hasLegacyContextChanged,
119 - pushContextProvider: pushLegacyContextProvider,
120 - pushTopLevelContextObject,
121 - invalidateContextProvider,
122 - } = legacyContext;
123 -
124 - const {
125 - enterHydrationState,
126 - resetHydrationState,
127 - tryToClaimNextHydratableInstance,
128 - } = hydrationContext;
129 -
130 - const {
131 - adoptClassInstance,
132 - constructClassInstance,
133 - mountClassInstance,
134 - resumeMountClassInstance,
135 - updateClassInstance,
136 - } = ReactFiberClassComponent(
137 - legacyContext,
138 - scheduleWork,
139 - computeExpirationForFiber,
140 - memoizeProps,
141 - memoizeState,
142 - recalculateCurrentTime,
112 +// TODO: Remove this and use reconcileChildrenAtExpirationTime directly.
113 +function reconcileChildren(current, workInProgress, nextChildren) {
114 + reconcileChildrenAtExpirationTime(
115 + current,
116 + workInProgress,
117 + nextChildren,
118 + workInProgress.expirationTime,
119 );
120 +}
121
145 - // TODO: Remove this and use reconcileChildrenAtExpirationTime directly.
146 - function reconcileChildren(current, workInProgress, nextChildren) {
147 - reconcileChildrenAtExpirationTime(
148 - current,
122 +function reconcileChildrenAtExpirationTime(
123 + current,
124 + workInProgress,
125 + nextChildren,
126 + renderExpirationTime,
127 +) {
128 + if (current === null) {
129 + // If this is a fresh new component that hasn't been rendered yet, we
130 + // won't update its child set by applying minimal side-effects. Instead,
131 + // we will add them all to the child before it gets rendered. That means
132 + // we can optimize this reconciliation pass by not tracking side-effects.
133 + workInProgress.child = mountChildFibers(
134 workInProgress,
135 + null,
136 nextChildren,
151 - workInProgress.expirationTime,
137 + renderExpirationTime,
138 + );
139 + } else {
140 + // If the current child is the same as the work in progress, it means that
141 + // we haven't yet started any work on these children. Therefore, we use
142 + // the clone algorithm to create a copy of all the current children.
143 +
144 + // If we had any progressed work already, that is invalid at this point so
145 + // let's throw it out.
146 + workInProgress.child = reconcileChildFibers(
147 + workInProgress,
148 + current.child,
149 + nextChildren,
150 + renderExpirationTime,
151 );
152 }
153 +}
154
155 - function reconcileChildrenAtExpirationTime(
156 - current,
157 - workInProgress,
158 - nextChildren,
159 - renderExpirationTime,
160 - ) {
161 - if (current === null) {
162 - // If this is a fresh new component that hasn't been rendered yet, we
163 - // won't update its child set by applying minimal side-effects. Instead,
164 - // we will add them all to the child before it gets rendered. That means
165 - // we can optimize this reconciliation pass by not tracking side-effects.
166 - workInProgress.child = mountChildFibers(
167 - workInProgress,
168 - null,
169 - nextChildren,
170 - renderExpirationTime,
171 - );
172 - } else {
173 - // If the current child is the same as the work in progress, it means that
174 - // we haven't yet started any work on these children. Therefore, we use
175 - // the clone algorithm to create a copy of all the current children.
176 -
177 - // If we had any progressed work already, that is invalid at this point so
178 - // let's throw it out.
179 - workInProgress.child = reconcileChildFibers(
180 - workInProgress,
181 - current.child,
182 - nextChildren,
183 - renderExpirationTime,
184 - );
155 +function updateForwardRef(current, workInProgress) {
156 + const render = workInProgress.type.render;
157 + const nextProps = workInProgress.pendingProps;
158 + const ref = workInProgress.ref;
159 + if (hasLegacyContextChanged()) {
160 + // Normally we can bail out on props equality but if context has changed
161 + // we don't do the bailout and we have to reuse existing props instead.
162 + } else if (workInProgress.memoizedProps === nextProps) {
163 + const currentRef = current !== null ? current.ref : null;
164 + if (ref === currentRef) {
165 + return bailoutOnAlreadyFinishedWork(current, workInProgress);
166 }
167 }
168
188 - function updateForwardRef(current, workInProgress) {
189 - const render = workInProgress.type.render;
190 - const nextProps = workInProgress.pendingProps;
191 - const ref = workInProgress.ref;
192 - if (hasLegacyContextChanged()) {
193 - // Normally we can bail out on props equality but if context has changed
194 - // we don't do the bailout and we have to reuse existing props instead.
195 - } else if (workInProgress.memoizedProps === nextProps) {
196 - const currentRef = current !== null ? current.ref : null;
197 - if (ref === currentRef) {
198 - return bailoutOnAlreadyFinishedWork(current, workInProgress);
199 - }
200 - }
201 -
202 - let nextChildren;
203 - if (__DEV__) {
204 - ReactCurrentOwner.current = workInProgress;
205 - ReactDebugCurrentFiber.setCurrentPhase('render');
206 - nextChildren = render(nextProps, ref);
207 - ReactDebugCurrentFiber.setCurrentPhase(null);
208 - } else {
209 - nextChildren = render(nextProps, ref);
210 - }
211 -
212 - reconcileChildren(current, workInProgress, nextChildren);
213 - memoizeProps(workInProgress, nextProps);
214 - return workInProgress.child;
169 + let nextChildren;
170 + if (__DEV__) {
171 + ReactCurrentOwner.current = workInProgress;
172 + ReactDebugCurrentFiber.setCurrentPhase('render');
173 + nextChildren = render(nextProps, ref);
174 + ReactDebugCurrentFiber.setCurrentPhase(null);
175 + } else {
176 + nextChildren = render(nextProps, ref);
177 }
178
217 - function updateFragment(current, workInProgress) {
218 - const nextChildren = workInProgress.pendingProps;
219 - if (hasLegacyContextChanged()) {
220 - // Normally we can bail out on props equality but if context has changed
221 - // we don't do the bailout and we have to reuse existing props instead.
222 - } else if (workInProgress.memoizedProps === nextChildren) {
223 - return bailoutOnAlreadyFinishedWork(current, workInProgress);
224 - }
225 - reconcileChildren(current, workInProgress, nextChildren);
226 - memoizeProps(workInProgress, nextChildren);
227 - return workInProgress.child;
179 + reconcileChildren(current, workInProgress, nextChildren);
180 + memoizeProps(workInProgress, nextProps);
181 + return workInProgress.child;
182 +}
183 +
184 +function updateFragment(current, workInProgress) {
185 + const nextChildren = workInProgress.pendingProps;
186 + if (hasLegacyContextChanged()) {
187 + // Normally we can bail out on props equality but if context has changed
188 + // we don't do the bailout and we have to reuse existing props instead.
189 + } else if (workInProgress.memoizedProps === nextChildren) {
190 + return bailoutOnAlreadyFinishedWork(current, workInProgress);
191 }
192 + reconcileChildren(current, workInProgress, nextChildren);
193 + memoizeProps(workInProgress, nextChildren);
194 + return workInProgress.child;
195 +}
196
230 - function updateMode(current, workInProgress) {
231 - const nextChildren = workInProgress.pendingProps.children;
232 - if (hasLegacyContextChanged()) {
233 - // Normally we can bail out on props equality but if context has changed
234 - // we don't do the bailout and we have to reuse existing props instead.
235 - } else if (
236 - nextChildren === null ||
237 - workInProgress.memoizedProps === nextChildren
238 - ) {
239 - return bailoutOnAlreadyFinishedWork(current, workInProgress);
240 - }
241 - reconcileChildren(current, workInProgress, nextChildren);
242 - memoizeProps(workInProgress, nextChildren);
243 - return workInProgress.child;
197 +function updateMode(current, workInProgress) {
198 + const nextChildren = workInProgress.pendingProps.children;
199 + if (hasLegacyContextChanged()) {
200 + // Normally we can bail out on props equality but if context has changed
201 + // we don't do the bailout and we have to reuse existing props instead.
202 + } else if (
203 + nextChildren === null ||
204 + workInProgress.memoizedProps === nextChildren
205 + ) {
206 + return bailoutOnAlreadyFinishedWork(current, workInProgress);
207 }
208 + reconcileChildren(current, workInProgress, nextChildren);
209 + memoizeProps(workInProgress, nextChildren);
210 + return workInProgress.child;
211 +}
212
246 - function updateProfiler(current, workInProgress) {
247 - const nextProps = workInProgress.pendingProps;
248 - if (enableProfilerTimer) {
249 - // Start render timer here and push start time onto queue
250 - markActualRenderTimeStarted(workInProgress);
213 +function updateProfiler(current, workInProgress) {
214 + const nextProps = workInProgress.pendingProps;
215 + if (enableProfilerTimer) {
216 + // Start render timer here and push start time onto queue
217 + markActualRenderTimeStarted(workInProgress);
218
252 - // Let the "complete" phase know to stop the timer,
253 - // And the scheduler to record the measured time.
254 - workInProgress.effectTag |= Update;
255 - }
256 - if (workInProgress.memoizedProps === nextProps) {
257 - return bailoutOnAlreadyFinishedWork(current, workInProgress);
258 - }
259 - const nextChildren = nextProps.children;
260 - reconcileChildren(current, workInProgress, nextChildren);
261 - memoizeProps(workInProgress, nextProps);
262 - return workInProgress.child;
219 + // Let the "complete" phase know to stop the timer,
220 + // And the scheduler to record the measured time.
221 + workInProgress.effectTag |= Update;
222 + }
223 + if (workInProgress.memoizedProps === nextProps) {
224 + return bailoutOnAlreadyFinishedWork(current, workInProgress);
225 }
226 + const nextChildren = nextProps.children;
227 + reconcileChildren(current, workInProgress, nextChildren);
228 + memoizeProps(workInProgress, nextProps);
229 + return workInProgress.child;
230 +}
231
265 - function markRef(current: Fiber | null, workInProgress: Fiber) {
266 - const ref = workInProgress.ref;
267 - if (
268 - (current === null && ref !== null) ||
269 - (current !== null && current.ref !== ref)
270 - ) {
271 - // Schedule a Ref effect
272 - workInProgress.effectTag |= Ref;
273 - }
232 +function markRef(current: Fiber | null, workInProgress: Fiber) {
233 + const ref = workInProgress.ref;
234 + if (
235 + (current === null && ref !== null) ||
236 + (current !== null && current.ref !== ref)
237 + ) {
238 + // Schedule a Ref effect
239 + workInProgress.effectTag |= Ref;
240 }
241 +}
242
276 - function updateFunctionalComponent(current, workInProgress) {
277 - const fn = workInProgress.type;
278 - const nextProps = workInProgress.pendingProps;
243 +function updateFunctionalComponent(current, workInProgress) {
244 + const fn = workInProgress.type;
245 + const nextProps = workInProgress.pendingProps;
246
280 - if (hasLegacyContextChanged()) {
281 - // Normally we can bail out on props equality but if context has changed
282 - // we don't do the bailout and we have to reuse existing props instead.
283 - } else {
284 - if (workInProgress.memoizedProps === nextProps) {
285 - return bailoutOnAlreadyFinishedWork(current, workInProgress);
286 - }
287 - // TODO: consider bringing fn.shouldComponentUpdate() back.
288 - // It used to be here.
247 + if (hasLegacyContextChanged()) {
248 + // Normally we can bail out on props equality but if context has changed
249 + // we don't do the bailout and we have to reuse existing props instead.
250 + } else {
251 + if (workInProgress.memoizedProps === nextProps) {
252 + return bailoutOnAlreadyFinishedWork(current, workInProgress);
253 }
254 + // TODO: consider bringing fn.shouldComponentUpdate() back.
255 + // It used to be here.
256 + }
257
291 - const unmaskedContext = getUnmaskedContext(workInProgress);
292 - const context = getMaskedContext(workInProgress, unmaskedContext);
258 + const unmaskedContext = getUnmaskedContext(workInProgress);
259 + const context = getMaskedContext(workInProgress, unmaskedContext);
260
294 - let nextChildren;
261 + let nextChildren;
262
296 - if (__DEV__) {
297 - ReactCurrentOwner.current = workInProgress;
298 - ReactDebugCurrentFiber.setCurrentPhase('render');
299 - nextChildren = fn(nextProps, context);
300 - ReactDebugCurrentFiber.setCurrentPhase(null);
301 - } else {
302 - nextChildren = fn(nextProps, context);
303 - }
304 - // React DevTools reads this flag.
305 - workInProgress.effectTag |= PerformedWork;
306 - reconcileChildren(current, workInProgress, nextChildren);
307 - memoizeProps(workInProgress, nextProps);
308 - return workInProgress.child;
263 + if (__DEV__) {
264 + ReactCurrentOwner.current = workInProgress;
265 + ReactDebugCurrentFiber.setCurrentPhase('render');
266 + nextChildren = fn(nextProps, context);
267 + ReactDebugCurrentFiber.setCurrentPhase(null);
268 + } else {
269 + nextChildren = fn(nextProps, context);
270 }
271 + // React DevTools reads this flag.
272 + workInProgress.effectTag |= PerformedWork;
273 + reconcileChildren(current, workInProgress, nextChildren);
274 + memoizeProps(workInProgress, nextProps);
275 + return workInProgress.child;
276 +}
277
311 - function updateClassComponent(
312 - current: Fiber | null,
313 - workInProgress: Fiber,
314 - renderExpirationTime: ExpirationTime,
315 - ) {
316 - // Push context providers early to prevent context stack mismatches.
317 - // During mounting we don't know the child context yet as the instance doesn't exist.
318 - // We will invalidate the child context in finishClassComponent() right after rendering.
319 - const hasContext = pushLegacyContextProvider(workInProgress);
320 - let shouldUpdate;
321 - if (current === null) {
322 - if (workInProgress.stateNode === null) {
323 - // In the initial pass we might need to construct the instance.
324 - constructClassInstance(
325 - workInProgress,
326 - workInProgress.pendingProps,
327 - renderExpirationTime,
328 - );
329 - mountClassInstance(workInProgress, renderExpirationTime);
278 +function updateClassComponent(
279 + current: Fiber | null,
280 + workInProgress: Fiber,
281 + renderExpirationTime: ExpirationTime,
282 +) {
283 + // Push context providers early to prevent context stack mismatches.
284 + // During mounting we don't know the child context yet as the instance doesn't exist.
285 + // We will invalidate the child context in finishClassComponent() right after rendering.
286 + const hasContext = pushLegacyContextProvider(workInProgress);
287 + let shouldUpdate;
288 + if (current === null) {
289 + if (workInProgress.stateNode === null) {
290 + // In the initial pass we might need to construct the instance.
291 + constructClassInstance(
292 + workInProgress,
293 + workInProgress.pendingProps,
294 + renderExpirationTime,
295 + );
296 + mountClassInstance(workInProgress, renderExpirationTime);
297
331 - shouldUpdate = true;
332 - } else {
333 - // In a resume, we'll already have an instance we can reuse.
334 - shouldUpdate = resumeMountClassInstance(
335 - workInProgress,
336 - renderExpirationTime,
337 - );
338 - }
298 + shouldUpdate = true;
299 } else {
340 - shouldUpdate = updateClassInstance(
341 - current,
300 + // In a resume, we'll already have an instance we can reuse.
301 + shouldUpdate = resumeMountClassInstance(
302 workInProgress,
303 renderExpirationTime,
304 );
305 }
346 - return finishClassComponent(
306 + } else {
307 + shouldUpdate = updateClassInstance(
308 current,
309 workInProgress,
349 - shouldUpdate,
350 - hasContext,
310 renderExpirationTime,
311 );
312 }
313 + return finishClassComponent(
314 + current,
315 + workInProgress,
316 + shouldUpdate,
317 + hasContext,
318 + renderExpirationTime,
319 + );
320 +}
321
355 - function finishClassComponent(
356 - current: Fiber | null,
357 - workInProgress: Fiber,
358 - shouldUpdate: boolean,
359 - hasContext: boolean,
360 - renderExpirationTime: ExpirationTime,
361 - ) {
362 - // Refs should update even if shouldComponentUpdate returns false
363 - markRef(current, workInProgress);
364 -
365 - const didCaptureError =
366 - (workInProgress.effectTag & DidCapture) !== NoEffect;
322 +function finishClassComponent(
323 + current: Fiber | null,
324 + workInProgress: Fiber,
325 + shouldUpdate: boolean,
326 + hasContext: boolean,
327 + renderExpirationTime: ExpirationTime,
328 +) {
329 + // Refs should update even if shouldComponentUpdate returns false
330 + markRef(current, workInProgress);
331
368 - if (!shouldUpdate && !didCaptureError) {
369 - // Context providers should defer to sCU for rendering
370 - if (hasContext) {
371 - invalidateContextProvider(workInProgress, false);
372 - }
332 + const didCaptureError = (workInProgress.effectTag & DidCapture) !== NoEffect;
333
374 - return bailoutOnAlreadyFinishedWork(current, workInProgress);
334 + if (!shouldUpdate && !didCaptureError) {
335 + // Context providers should defer to sCU for rendering
336 + if (hasContext) {
337 + invalidateContextProvider(workInProgress, false);
338 }
339
377 - const ctor = workInProgress.type;
378 - const instance = workInProgress.stateNode;
340 + return bailoutOnAlreadyFinishedWork(current, workInProgress);
341 + }
342
380 - // Rerender
381 - ReactCurrentOwner.current = workInProgress;
382 - let nextChildren;
383 - if (
384 - didCaptureError &&
385 - (!enableGetDerivedStateFromCatch ||
386 - typeof ctor.getDerivedStateFromCatch !== 'function')
387 - ) {
388 - // If we captured an error, but getDerivedStateFrom catch is not defined,
389 - // unmount all the children. componentDidCatch will schedule an update to
390 - // re-render a fallback. This is temporary until we migrate everyone to
391 - // the new API.
392 - // TODO: Warn in a future release.
393 - nextChildren = null;
343 + const ctor = workInProgress.type;
344 + const instance = workInProgress.stateNode;
345
395 - if (enableProfilerTimer) {
396 - stopBaseRenderTimerIfRunning();
346 + // Rerender
347 + ReactCurrentOwner.current = workInProgress;
348 + let nextChildren;
349 + if (
350 + didCaptureError &&
351 + (!enableGetDerivedStateFromCatch ||
352 + typeof ctor.getDerivedStateFromCatch !== 'function')
353 + ) {
354 + // If we captured an error, but getDerivedStateFrom catch is not defined,
355 + // unmount all the children. componentDidCatch will schedule an update to
356 + // re-render a fallback. This is temporary until we migrate everyone to
357 + // the new API.
358 + // TODO: Warn in a future release.
359 + nextChildren = null;
360 +
361 + if (enableProfilerTimer) {
362 + stopBaseRenderTimerIfRunning();
363 + }
364 + } else {
365 + if (__DEV__) {
366 + ReactDebugCurrentFiber.setCurrentPhase('render');
367 + nextChildren = instance.render();
368 + if (
369 + debugRenderPhaseSideEffects ||
370 + (debugRenderPhaseSideEffectsForStrictMode &&
371 + workInProgress.mode & StrictMode)
372 + ) {
373 + instance.render();
374 }
375 + ReactDebugCurrentFiber.setCurrentPhase(null);
376 } else {
399 - if (__DEV__) {
400 - ReactDebugCurrentFiber.setCurrentPhase('render');
401 - nextChildren = instance.render();
402 - if (
403 - debugRenderPhaseSideEffects ||
404 - (debugRenderPhaseSideEffectsForStrictMode &&
405 - workInProgress.mode & StrictMode)
406 - ) {
407 - instance.render();
408 - }
409 - ReactDebugCurrentFiber.setCurrentPhase(null);
410 - } else {
411 - nextChildren = instance.render();
412 - }
377 + nextChildren = instance.render();
378 }
379 + }
380
415 - // React DevTools reads this flag.
416 - workInProgress.effectTag |= PerformedWork;
417 - if (didCaptureError) {
418 - // If we're recovering from an error, reconcile twice: first to delete
419 - // all the existing children.
420 - reconcileChildrenAtExpirationTime(
421 - current,
422 - workInProgress,
423 - null,
424 - renderExpirationTime,
425 - );
426 - workInProgress.child = null;
427 - // Now we can continue reconciling like normal. This has the effect of
428 - // remounting all children regardless of whether their their
429 - // identity matches.
430 - }
381 + // React DevTools reads this flag.
382 + workInProgress.effectTag |= PerformedWork;
383 + if (didCaptureError) {
384 + // If we're recovering from an error, reconcile twice: first to delete
385 + // all the existing children.
386 reconcileChildrenAtExpirationTime(
387 current,
388 workInProgress,
434 - nextChildren,
389 + null,
390 renderExpirationTime,
391 );
437 - // Memoize props and state using the values we just used to render.
438 - // TODO: Restructure so we never read values from the instance.
439 - memoizeState(workInProgress, instance.state);
440 - memoizeProps(workInProgress, instance.props);
392 + workInProgress.child = null;
393 + // Now we can continue reconciling like normal. This has the effect of
394 + // remounting all children regardless of whether their their
395 + // identity matches.
396 + }
397 + reconcileChildrenAtExpirationTime(
398 + current,
399 + workInProgress,
400 + nextChildren,
401 + renderExpirationTime,
402 + );
403 + // Memoize props and state using the values we just used to render.
404 + // TODO: Restructure so we never read values from the instance.
405 + memoizeState(workInProgress, instance.state);
406 + memoizeProps(workInProgress, instance.props);
407 +
408 + // The context might have changed so we need to recalculate it.
409 + if (hasContext) {
410 + invalidateContextProvider(workInProgress, true);
411 + }
412
442 - // The context might have changed so we need to recalculate it.
443 - if (hasContext) {
444 - invalidateContextProvider(workInProgress, true);
445 - }
413 + return workInProgress.child;
414 +}
415
447 - return workInProgress.child;
416 +function pushHostRootContext(workInProgress) {
417 + const root = (workInProgress.stateNode: FiberRoot);
418 + if (root.pendingContext) {
419 + pushTopLevelContextObject(
420 + workInProgress,
421 + root.pendingContext,
422 + root.pendingContext !== root.context,
423 + );
424 + } else if (root.context) {
425 + // Should always be set
426 + pushTopLevelContextObject(workInProgress, root.context, false);
427 }
428 + pushHostContainer(workInProgress, root.containerInfo);
429 +}
430
450 - function pushHostRootContext(workInProgress) {
451 - const root = (workInProgress.stateNode: FiberRoot);
452 - if (root.pendingContext) {
453 - pushTopLevelContextObject(
454 - workInProgress,
455 - root.pendingContext,
456 - root.pendingContext !== root.context,
457 - );
458 - } else if (root.context) {
459 - // Should always be set
460 - pushTopLevelContextObject(workInProgress, root.context, false);
431 +function updateHostRoot(current, workInProgress, renderExpirationTime) {
432 + pushHostRootContext(workInProgress);
433 + let updateQueue = workInProgress.updateQueue;
434 + if (updateQueue !== null) {
435 + const nextProps = workInProgress.pendingProps;
436 + const prevState = workInProgress.memoizedState;
437 + const prevChildren = prevState !== null ? prevState.element : null;
438 + processUpdateQueue(
439 + workInProgress,
440 + updateQueue,
441 + nextProps,
442 + null,
443 + renderExpirationTime,
444 + );
445 + const nextState = workInProgress.memoizedState;
446 + // Caution: React DevTools currently depends on this property
447 + // being called "element".
448 + const nextChildren = nextState.element;
449 +
450 + if (nextChildren === prevChildren) {
451 + // If the state is the same as before, that's a bailout because we had
452 + // no work that expires at this time.
453 + resetHydrationState();
454 + return bailoutOnAlreadyFinishedWork(current, workInProgress);
455 }
462 - pushHostContainer(workInProgress, root.containerInfo);
463 - }
464 -
465 - function updateHostRoot(current, workInProgress, renderExpirationTime) {
466 - pushHostRootContext(workInProgress);
467 - let updateQueue = workInProgress.updateQueue;
468 - if (updateQueue !== null) {
469 - const nextProps = workInProgress.pendingProps;
470 - const prevState = workInProgress.memoizedState;
471 - const prevChildren = prevState !== null ? prevState.element : null;
472 - processUpdateQueue(
456 + const root: FiberRoot = workInProgress.stateNode;
457 + if (
458 + (current === null || current.child === null) &&
459 + root.hydrate &&
460 + enterHydrationState(workInProgress)
461 + ) {
462 + // If we don't have any current children this might be the first pass.
463 + // We always try to hydrate. If this isn't a hydration pass there won't
464 + // be any children to hydrate which is effectively the same thing as
465 + // not hydrating.
466 +
467 + // This is a bit of a hack. We track the host root as a placement to
468 + // know that we're currently in a mounting state. That way isMounted
469 + // works as expected. We must reset this before committing.
470 + // TODO: Delete this when we delete isMounted and findDOMNode.
471 + workInProgress.effectTag |= Placement;
472 +
473 + // Ensure that children mount into this root without tracking
474 + // side-effects. This ensures that we don't store Placement effects on
475 + // nodes that will be hydrated.
476 + workInProgress.child = mountChildFibers(
477 workInProgress,
474 - updateQueue,
475 - nextProps,
478 null,
479 + nextChildren,
480 renderExpirationTime,
481 );
479 - const nextState = workInProgress.memoizedState;
480 - // Caution: React DevTools currently depends on this property
481 - // being called "element".
482 - const nextChildren = nextState.element;
483 -
484 - if (nextChildren === prevChildren) {
485 - // If the state is the same as before, that's a bailout because we had
486 - // no work that expires at this time.
487 - resetHydrationState();
488 - return bailoutOnAlreadyFinishedWork(current, workInProgress);
489 - }
490 - const root: FiberRoot = workInProgress.stateNode;
491 - if (
492 - (current === null || current.child === null) &&
493 - root.hydrate &&
494 - enterHydrationState(workInProgress)
495 - ) {
496 - // If we don't have any current children this might be the first pass.
497 - // We always try to hydrate. If this isn't a hydration pass there won't
498 - // be any children to hydrate which is effectively the same thing as
499 - // not hydrating.
500 -
501 - // This is a bit of a hack. We track the host root as a placement to
502 - // know that we're currently in a mounting state. That way isMounted
503 - // works as expected. We must reset this before committing.
504 - // TODO: Delete this when we delete isMounted and findDOMNode.
505 - workInProgress.effectTag |= Placement;
506 -
507 - // Ensure that children mount into this root without tracking
508 - // side-effects. This ensures that we don't store Placement effects on
509 - // nodes that will be hydrated.
510 - workInProgress.child = mountChildFibers(
511 - workInProgress,
512 - null,
513 - nextChildren,
514 - renderExpirationTime,
515 - );
516 - } else {
517 - // Otherwise reset hydration state in case we aborted and resumed another
518 - // root.
519 - resetHydrationState();
520 - reconcileChildren(current, workInProgress, nextChildren);
521 - }
522 - return workInProgress.child;
482 + } else {
483 + // Otherwise reset hydration state in case we aborted and resumed another
484 + // root.
485 + resetHydrationState();
486 + reconcileChildren(current, workInProgress, nextChildren);
487 }
524 - resetHydrationState();
525 - // If there is no update queue, that's a bailout because the root has no props.
526 - return bailoutOnAlreadyFinishedWork(current, workInProgress);
488 + return workInProgress.child;
489 }
490 + resetHydrationState();
491 + // If there is no update queue, that's a bailout because the root has no props.
492 + return bailoutOnAlreadyFinishedWork(current, workInProgress);
493 +}
494
529 - function updateHostComponent(current, workInProgress, renderExpirationTime) {
530 - pushHostContext(workInProgress);
531 -
532 - if (current === null) {
533 - tryToClaimNextHydratableInstance(workInProgress);
534 - }
535 -
536 - const type = workInProgress.type;
537 - const memoizedProps = workInProgress.memoizedProps;
538 - const nextProps = workInProgress.pendingProps;
539 - const prevProps = current !== null ? current.memoizedProps : null;
540 -
541 - if (hasLegacyContextChanged()) {
542 - // Normally we can bail out on props equality but if context has changed
543 - // we don't do the bailout and we have to reuse existing props instead.
544 - } else if (memoizedProps === nextProps) {
545 - const isHidden =
546 - workInProgress.mode & AsyncMode &&
547 - shouldDeprioritizeSubtree(type, nextProps);
548 - if (isHidden) {
549 - // Before bailing out, make sure we've deprioritized a hidden component.
550 - workInProgress.expirationTime = Never;
551 - }
552 - if (!isHidden || renderExpirationTime !== Never) {
553 - return bailoutOnAlreadyFinishedWork(current, workInProgress);
554 - }
555 - // If we're rendering a hidden node at hidden priority, don't bailout. The
556 - // parent is complete, but the children may not be.
557 - }
495 +function updateHostComponent(current, workInProgress, renderExpirationTime) {
496 + pushHostContext(workInProgress);
497
559 - let nextChildren = nextProps.children;
560 - const isDirectTextChild = shouldSetTextContent(type, nextProps);
561 -
562 - if (isDirectTextChild) {
563 - // We special case a direct text child of a host node. This is a common
564 - // case. We won't handle it as a reified child. We will instead handle
565 - // this in the host environment that also have access to this prop. That
566 - // avoids allocating another HostText fiber and traversing it.
567 - nextChildren = null;
568 - } else if (prevProps && shouldSetTextContent(type, prevProps)) {
569 - // If we're switching from a direct text child to a normal child, or to
570 - // empty, we need to schedule the text content to be reset.
571 - workInProgress.effectTag |= ContentReset;
572 - }
498 + if (current === null) {
499 + tryToClaimNextHydratableInstance(workInProgress);
500 + }
501
574 - markRef(current, workInProgress);
502 + const type = workInProgress.type;
503 + const memoizedProps = workInProgress.memoizedProps;
504 + const nextProps = workInProgress.pendingProps;
505 + const prevProps = current !== null ? current.memoizedProps : null;
506
576 - // Check the host config to see if the children are offscreen/hidden.
577 - if (
578 - renderExpirationTime !== Never &&
507 + if (hasLegacyContextChanged()) {
508 + // Normally we can bail out on props equality but if context has changed
509 + // we don't do the bailout and we have to reuse existing props instead.
510 + } else if (memoizedProps === nextProps) {
511 + const isHidden =
512 workInProgress.mode & AsyncMode &&
580 - shouldDeprioritizeSubtree(type, nextProps)
581 - ) {
582 - // Down-prioritize the children.
513 + shouldDeprioritizeSubtree(type, nextProps);
514 + if (isHidden) {
515 + // Before bailing out, make sure we've deprioritized a hidden component.
516 workInProgress.expirationTime = Never;
584 - // Bailout and come back to this fiber later.
585 - workInProgress.memoizedProps = nextProps;
586 - return null;
517 }
518 + if (!isHidden || renderExpirationTime !== Never) {
519 + return bailoutOnAlreadyFinishedWork(current, workInProgress);
520 + }
521 + // If we're rendering a hidden node at hidden priority, don't bailout. The
522 + // parent is complete, but the children may not be.
523 + }
524
589 - reconcileChildren(current, workInProgress, nextChildren);
590 - memoizeProps(workInProgress, nextProps);
591 - return workInProgress.child;
525 + let nextChildren = nextProps.children;
526 + const isDirectTextChild = shouldSetTextContent(type, nextProps);
527 +
528 + if (isDirectTextChild) {
529 + // We special case a direct text child of a host node. This is a common
530 + // case. We won't handle it as a reified child. We will instead handle
531 + // this in the host environment that also have access to this prop. That
532 + // avoids allocating another HostText fiber and traversing it.
533 + nextChildren = null;
534 + } else if (prevProps && shouldSetTextContent(type, prevProps)) {
535 + // If we're switching from a direct text child to a normal child, or to
536 + // empty, we need to schedule the text content to be reset.
537 + workInProgress.effectTag |= ContentReset;
538 }
539
594 - function updateHostText(current, workInProgress) {
595 - if (current === null) {
596 - tryToClaimNextHydratableInstance(workInProgress);
597 - }
598 - const nextProps = workInProgress.pendingProps;
599 - memoizeProps(workInProgress, nextProps);
600 - // Nothing to do here. This is terminal. We'll do the completion step
601 - // immediately after.
540 + markRef(current, workInProgress);
541 +
542 + // Check the host config to see if the children are offscreen/hidden.
543 + if (
544 + renderExpirationTime !== Never &&
545 + workInProgress.mode & AsyncMode &&
546 + shouldDeprioritizeSubtree(type, nextProps)
547 + ) {
548 + // Down-prioritize the children.
549 + workInProgress.expirationTime = Never;
550 + // Bailout and come back to this fiber later.
551 + workInProgress.memoizedProps = nextProps;
552 return null;
553 }
554
605 - function mountIndeterminateComponent(
606 - current,
607 - workInProgress,
608 - renderExpirationTime,
609 - ) {
610 - invariant(
611 - current === null,
612 - 'An indeterminate component should never have mounted. This error is ' +
613 - 'likely caused by a bug in React. Please file an issue.',
614 - );
615 - const fn = workInProgress.type;
616 - const props = workInProgress.pendingProps;
617 - const unmaskedContext = getUnmaskedContext(workInProgress);
618 - const context = getMaskedContext(workInProgress, unmaskedContext);
555 + reconcileChildren(current, workInProgress, nextChildren);
556 + memoizeProps(workInProgress, nextProps);
557 + return workInProgress.child;
558 +}
559
620 - let value;
560 +function updateHostText(current, workInProgress) {
561 + if (current === null) {
562 + tryToClaimNextHydratableInstance(workInProgress);
563 + }
564 + const nextProps = workInProgress.pendingProps;
565 + memoizeProps(workInProgress, nextProps);
566 + // Nothing to do here. This is terminal. We'll do the completion step
567 + // immediately after.
568 + return null;
569 +}
570
622 - if (__DEV__) {
623 - if (fn.prototype && typeof fn.prototype.render === 'function') {
624 - const componentName = getComponentName(workInProgress) || 'Unknown';
571 +function mountIndeterminateComponent(
572 + current,
573 + workInProgress,
574 + renderExpirationTime,
575 +) {
576 + invariant(
577 + current === null,
578 + 'An indeterminate component should never have mounted. This error is ' +
579 + 'likely caused by a bug in React. Please file an issue.',
580 + );
581 + const fn = workInProgress.type;
582 + const props = workInProgress.pendingProps;
583 + const unmaskedContext = getUnmaskedContext(workInProgress);
584 + const context = getMaskedContext(workInProgress, unmaskedContext);
585
626 - if (!didWarnAboutBadClass[componentName]) {
627 - warning(
628 - false,
629 - "The <%s /> component appears to have a render method, but doesn't extend React.Component. " +
630 - 'This is likely to cause errors. Change %s to extend React.Component instead.',
631 - componentName,
632 - componentName,
633 - );
634 - didWarnAboutBadClass[componentName] = true;
635 - }
586 + let value;
587 +
588 + if (__DEV__) {
589 + if (fn.prototype && typeof fn.prototype.render === 'function') {
590 + const componentName = getComponentName(workInProgress) || 'Unknown';
591 +
592 + if (!didWarnAboutBadClass[componentName]) {
593 + warning(
594 + false,
595 + "The <%s /> component appears to have a render method, but doesn't extend React.Component. " +
596 + 'This is likely to cause errors. Change %s to extend React.Component instead.',
597 + componentName,
598 + componentName,
599 + );
600 + didWarnAboutBadClass[componentName] = true;
601 }
637 - ReactCurrentOwner.current = workInProgress;
638 - value = fn(props, context);
639 - } else {
640 - value = fn(props, context);
602 }
642 - // React DevTools reads this flag.
643 - workInProgress.effectTag |= PerformedWork;
603 + ReactCurrentOwner.current = workInProgress;
604 + value = fn(props, context);
605 + } else {
606 + value = fn(props, context);
607 + }
608 + // React DevTools reads this flag.
609 + workInProgress.effectTag |= PerformedWork;
610 +
611 + if (
612 + typeof value === 'object' &&
613 + value !== null &&
614 + typeof value.render === 'function' &&
615 + value.$$typeof === undefined
616 + ) {
617 + const Component = workInProgress.type;
618
645 - if (
646 - typeof value === 'object' &&
647 - value !== null &&
648 - typeof value.render === 'function' &&
649 - value.$$typeof === undefined
650 - ) {
651 - const Component = workInProgress.type;
619 + // Proceed under the assumption that this is a class instance
620 + workInProgress.tag = ClassComponent;
621
653 - // Proceed under the assumption that this is a class instance
654 - workInProgress.tag = ClassComponent;
622 + workInProgress.memoizedState =
623 + value.state !== null && value.state !== undefined ? value.state : null;
624 +
625 + const getDerivedStateFromProps = Component.getDerivedStateFromProps;
626 + if (typeof getDerivedStateFromProps === 'function') {
627 + applyDerivedStateFromProps(
628 + workInProgress,
629 + getDerivedStateFromProps,
630 + props,
631 + );
632 + }
633
656 - workInProgress.memoizedState =
657 - value.state !== null && value.state !== undefined ? value.state : null;
634 + // Push context providers early to prevent context stack mismatches.
635 + // During mounting we don't know the child context yet as the instance doesn't exist.
636 + // We will invalidate the child context in finishClassComponent() right after rendering.
637 + const hasContext = pushLegacyContextProvider(workInProgress);
638 + adoptClassInstance(workInProgress, value);
639 + mountClassInstance(workInProgress, renderExpirationTime);
640 + return finishClassComponent(
641 + current,
642 + workInProgress,
643 + true,
644 + hasContext,
645 + renderExpirationTime,
646 + );
647 + } else {
648 + // Proceed under the assumption that this is a functional component
649 + workInProgress.tag = FunctionalComponent;
650 + if (__DEV__) {
651 + const Component = workInProgress.type;
652
659 - const getDerivedStateFromProps = Component.getDerivedStateFromProps;
660 - if (typeof getDerivedStateFromProps === 'function') {
661 - applyDerivedStateFromProps(
662 - workInProgress,
663 - getDerivedStateFromProps,
664 - props,
653 + if (Component) {
654 + warning(
655 + !Component.childContextTypes,
656 + '%s(...): childContextTypes cannot be defined on a functional component.',
657 + Component.displayName || Component.name || 'Component',
658 );
659 }
660 + if (workInProgress.ref !== null) {
661 + let info = '';
662 + const ownerName = ReactDebugCurrentFiber.getCurrentFiberOwnerName();
663 + if (ownerName) {
664 + info += '\n\nCheck the render method of `' + ownerName + '`.';
665 + }
666
668 - // Push context providers early to prevent context stack mismatches.
669 - // During mounting we don't know the child context yet as the instance doesn't exist.
670 - // We will invalidate the child context in finishClassComponent() right after rendering.
671 - const hasContext = pushLegacyContextProvider(workInProgress);
672 - adoptClassInstance(workInProgress, value);
673 - mountClassInstance(workInProgress, renderExpirationTime);
674 - return finishClassComponent(
675 - current,
676 - workInProgress,
677 - true,
678 - hasContext,
679 - renderExpirationTime,
680 - );
681 - } else {
682 - // Proceed under the assumption that this is a functional component
683 - workInProgress.tag = FunctionalComponent;
684 - if (__DEV__) {
685 - const Component = workInProgress.type;
686 -
687 - if (Component) {
667 + let warningKey = ownerName || workInProgress._debugID || '';
668 + const debugSource = workInProgress._debugSource;
669 + if (debugSource) {
670 + warningKey = debugSource.fileName + ':' + debugSource.lineNumber;
671 + }
672 + if (!didWarnAboutStatelessRefs[warningKey]) {
673 + didWarnAboutStatelessRefs[warningKey] = true;
674 warning(
689 - !Component.childContextTypes,
690 - '%s(...): childContextTypes cannot be defined on a functional component.',
691 - Component.displayName || Component.name || 'Component',
675 + false,
676 + 'Stateless function components cannot be given refs. ' +
677 + 'Attempts to access this ref will fail.%s%s',
678 + info,
679 + ReactDebugCurrentFiber.getCurrentFiberStackAddendum(),
680 );
681 }
694 - if (workInProgress.ref !== null) {
695 - let info = '';
696 - const ownerName = ReactDebugCurrentFiber.getCurrentFiberOwnerName();
697 - if (ownerName) {
698 - info += '\n\nCheck the render method of `' + ownerName + '`.';
699 - }
682 + }
683
701 - let warningKey = ownerName || workInProgress._debugID || '';
702 - const debugSource = workInProgress._debugSource;
703 - if (debugSource) {
704 - warningKey = debugSource.fileName + ':' + debugSource.lineNumber;
705 - }
706 - if (!didWarnAboutStatelessRefs[warningKey]) {
707 - didWarnAboutStatelessRefs[warningKey] = true;
708 - warning(
709 - false,
710 - 'Stateless function components cannot be given refs. ' +
711 - 'Attempts to access this ref will fail.%s%s',
712 - info,
713 - ReactDebugCurrentFiber.getCurrentFiberStackAddendum(),
714 - );
715 - }
716 - }
684 + if (typeof fn.getDerivedStateFromProps === 'function') {
685 + const componentName = getComponentName(workInProgress) || 'Unknown';
686
718 - if (typeof fn.getDerivedStateFromProps === 'function') {
719 - const componentName = getComponentName(workInProgress) || 'Unknown';
720 -
721 - if (
722 - !didWarnAboutGetDerivedStateOnFunctionalComponent[componentName]
723 - ) {
724 - warning(
725 - false,
726 - '%s: Stateless functional components do not support getDerivedStateFromProps.',
727 - componentName,
728 - );
729 - didWarnAboutGetDerivedStateOnFunctionalComponent[
730 - componentName
731 - ] = true;
732 - }
687 + if (!didWarnAboutGetDerivedStateOnFunctionalComponent[componentName]) {
688 + warning(
689 + false,
690 + '%s: Stateless functional components do not support getDerivedStateFromProps.',
691 + componentName,
692 + );
693 + didWarnAboutGetDerivedStateOnFunctionalComponent[
694 + componentName
695 + ] = true;
696 }
697 }
735 - reconcileChildren(current, workInProgress, value);
736 - memoizeProps(workInProgress, props);
737 - return workInProgress.child;
698 }
699 + reconcileChildren(current, workInProgress, value);
700 + memoizeProps(workInProgress, props);
701 + return workInProgress.child;
702 }
703 +}
704
741 - function updateTimeoutComponent(
742 - current,
743 - workInProgress,
744 - renderExpirationTime,
745 - ) {
746 - if (enableSuspense) {
747 - const nextProps = workInProgress.pendingProps;
748 - const prevProps = workInProgress.memoizedProps;
749 -
750 - const prevDidTimeout = workInProgress.memoizedState;
751 -
752 - // Check if we already attempted to render the normal state. If we did,
753 - // and we timed out, render the placeholder state.
754 - const alreadyCaptured =
755 - (workInProgress.effectTag & DidCapture) === NoEffect;
756 - const nextDidTimeout = !alreadyCaptured;
757 -
758 - if (hasLegacyContextChanged()) {
759 - // Normally we can bail out on props equality but if context has changed
760 - // we don't do the bailout and we have to reuse existing props instead.
761 - } else if (nextProps === prevProps && nextDidTimeout === prevDidTimeout) {
762 - return bailoutOnAlreadyFinishedWork(current, workInProgress);
763 - }
705 +function updateTimeoutComponent(current, workInProgress, renderExpirationTime) {
706 + if (enableSuspense) {
707 + const nextProps = workInProgress.pendingProps;
708 + const prevProps = workInProgress.memoizedProps;
709
765 - const render = nextProps.children;
766 - const nextChildren = render(nextDidTimeout);
767 - workInProgress.memoizedProps = nextProps;
768 - workInProgress.memoizedState = nextDidTimeout;
769 - reconcileChildren(current, workInProgress, nextChildren);
770 - return workInProgress.child;
771 - } else {
772 - return null;
773 - }
774 - }
710 + const prevDidTimeout = workInProgress.memoizedState;
711 +
712 + // Check if we already attempted to render the normal state. If we did,
713 + // and we timed out, render the placeholder state.
714 + const alreadyCaptured =
715 + (workInProgress.effectTag & DidCapture) === NoEffect;
716 + const nextDidTimeout = !alreadyCaptured;
717
776 - function updatePortalComponent(
777 - current,
778 - workInProgress,
779 - renderExpirationTime,
780 - ) {
781 - pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
782 - const nextChildren = workInProgress.pendingProps;
718 if (hasLegacyContextChanged()) {
719 // Normally we can bail out on props equality but if context has changed
720 // we don't do the bailout and we have to reuse existing props instead.
786 - } else if (workInProgress.memoizedProps === nextChildren) {
721 + } else if (nextProps === prevProps && nextDidTimeout === prevDidTimeout) {
722 return bailoutOnAlreadyFinishedWork(current, workInProgress);
723 }
724
790 - if (current === null) {
791 - // Portals are special because we don't append the children during mount
792 - // but at commit. Therefore we need to track insertions which the normal
793 - // flow doesn't do during mount. This doesn't happen at the root because
794 - // the root always starts with a "current" with a null child.
795 - // TODO: Consider unifying this with how the root works.
796 - workInProgress.child = reconcileChildFibers(
797 - workInProgress,
798 - null,
799 - nextChildren,
800 - renderExpirationTime,
801 - );
802 - memoizeProps(workInProgress, nextChildren);
803 - } else {
804 - reconcileChildren(current, workInProgress, nextChildren);
805 - memoizeProps(workInProgress, nextChildren);
806 - }
725 + const render = nextProps.children;
726 + const nextChildren = render(nextDidTimeout);
727 + workInProgress.memoizedProps = nextProps;
728 + workInProgress.memoizedState = nextDidTimeout;
729 + reconcileChildren(current, workInProgress, nextChildren);
730 return workInProgress.child;
731 + } else {
732 + return null;
733 }
734 +}
735
810 - function propagateContextChange<V>(
811 - workInProgress: Fiber,
812 - context: ReactContext<V>,
813 - changedBits: number,
814 - renderExpirationTime: ExpirationTime,
815 - ): void {
816 - let fiber = workInProgress.child;
817 - if (fiber !== null) {
818 - // Set the return pointer of the child to the work-in-progress fiber.
819 - fiber.return = workInProgress;
820 - }
821 - while (fiber !== null) {
822 - let nextFiber;
823 - // Visit this fiber.
824 - switch (fiber.tag) {
825 - case ContextConsumer:
826 - // Check if the context matches.
827 - const observedBits: number = fiber.stateNode | 0;
828 - if (fiber.type === context && (observedBits & changedBits) !== 0) {
829 - // Update the expiration time of all the ancestors, including
830 - // the alternates.
831 - let node = fiber;
832 - while (node !== null) {
833 - const alternate = node.alternate;
736 +function updatePortalComponent(current, workInProgress, renderExpirationTime) {
737 + pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
738 + const nextChildren = workInProgress.pendingProps;
739 + if (hasLegacyContextChanged()) {
740 + // Normally we can bail out on props equality but if context has changed
741 + // we don't do the bailout and we have to reuse existing props instead.
742 + } else if (workInProgress.memoizedProps === nextChildren) {
743 + return bailoutOnAlreadyFinishedWork(current, workInProgress);
744 + }
745 +
746 + if (current === null) {
747 + // Portals are special because we don't append the children during mount
748 + // but at commit. Therefore we need to track insertions which the normal
749 + // flow doesn't do during mount. This doesn't happen at the root because
750 + // the root always starts with a "current" with a null child.
751 + // TODO: Consider unifying this with how the root works.
752 + workInProgress.child = reconcileChildFibers(
753 + workInProgress,
754 + null,
755 + nextChildren,
756 + renderExpirationTime,
757 + );
758 + memoizeProps(workInProgress, nextChildren);
759 + } else {
760 + reconcileChildren(current, workInProgress, nextChildren);
761 + memoizeProps(workInProgress, nextChildren);
762 + }
763 + return workInProgress.child;
764 +}
765 +
766 +function propagateContextChange<V>(
767 + workInProgress: Fiber,
768 + context: ReactContext<V>,
769 + changedBits: number,
770 + renderExpirationTime: ExpirationTime,
771 +): void {
772 + let fiber = workInProgress.child;
773 + if (fiber !== null) {
774 + // Set the return pointer of the child to the work-in-progress fiber.
775 + fiber.return = workInProgress;
776 + }
777 + while (fiber !== null) {
778 + let nextFiber;
779 + // Visit this fiber.
780 + switch (fiber.tag) {
781 + case ContextConsumer:
782 + // Check if the context matches.
783 + const observedBits: number = fiber.stateNode | 0;
784 + if (fiber.type === context && (observedBits & changedBits) !== 0) {
785 + // Update the expiration time of all the ancestors, including
786 + // the alternates.
787 + let node = fiber;
788 + while (node !== null) {
789 + const alternate = node.alternate;
790 + if (
791 + node.expirationTime === NoWork ||
792 + node.expirationTime > renderExpirationTime
793 + ) {
794 + node.expirationTime = renderExpirationTime;
795 if (
835 - node.expirationTime === NoWork ||
836 - node.expirationTime > renderExpirationTime
837 - ) {
838 - node.expirationTime = renderExpirationTime;
839 - if (
840 - alternate !== null &&
841 - (alternate.expirationTime === NoWork ||
842 - alternate.expirationTime > renderExpirationTime)
843 - ) {
844 - alternate.expirationTime = renderExpirationTime;
845 - }
846 - } else if (
796 alternate !== null &&
797 (alternate.expirationTime === NoWork ||
798 alternate.expirationTime > renderExpirationTime)
799 ) {
800 alternate.expirationTime = renderExpirationTime;
852 - } else {
853 - // Neither alternate was updated, which means the rest of the
854 - // ancestor path already has sufficient priority.
855 - break;
801 }
857 - node = node.return;
802 + } else if (
803 + alternate !== null &&
804 + (alternate.expirationTime === NoWork ||
805 + alternate.expirationTime > renderExpirationTime)
806 + ) {
807 + alternate.expirationTime = renderExpirationTime;
808 + } else {
809 + // Neither alternate was updated, which means the rest of the
810 + // ancestor path already has sufficient priority.
811 + break;
812 }
859 - // Don't scan deeper than a matching consumer. When we render the
860 - // consumer, we'll continue scanning from that point. This way the
861 - // scanning work is time-sliced.
862 - nextFiber = null;
863 - } else {
864 - // Traverse down.
865 - nextFiber = fiber.child;
813 + node = node.return;
814 }
867 - break;
868 - case ContextProvider:
869 - // Don't scan deeper if this is a matching provider
870 - nextFiber = fiber.type === workInProgress.type ? null : fiber.child;
871 - break;
872 - default:
815 + // Don't scan deeper than a matching consumer. When we render the
816 + // consumer, we'll continue scanning from that point. This way the
817 + // scanning work is time-sliced.
818 + nextFiber = null;
819 + } else {
820 // Traverse down.
821 nextFiber = fiber.child;
822 + }
823 + break;
824 + case ContextProvider:
825 + // Don't scan deeper if this is a matching provider
826 + nextFiber = fiber.type === workInProgress.type ? null : fiber.child;
827 + break;
828 + default:
829 + // Traverse down.
830 + nextFiber = fiber.child;
831 + break;
832 + }
833 + if (nextFiber !== null) {
834 + // Set the return pointer of the child to the work-in-progress fiber.
835 + nextFiber.return = fiber;
836 + } else {
837 + // No child. Traverse to next sibling.
838 + nextFiber = fiber;
839 + while (nextFiber !== null) {
840 + if (nextFiber === workInProgress) {
841 + // We're back to the root of this subtree. Exit.
842 + nextFiber = null;
843 + break;
844 + }
845 + let sibling = nextFiber.sibling;
846 + if (sibling !== null) {
847 + // Set the return pointer of the sibling to the work-in-progress fiber.
848 + sibling.return = nextFiber.return;
849 + nextFiber = sibling;
850 break;
876 - }
877 - if (nextFiber !== null) {
878 - // Set the return pointer of the child to the work-in-progress fiber.
879 - nextFiber.return = fiber;
880 - } else {
881 - // No child. Traverse to next sibling.
882 - nextFiber = fiber;
883 - while (nextFiber !== null) {
884 - if (nextFiber === workInProgress) {
885 - // We're back to the root of this subtree. Exit.
886 - nextFiber = null;
887 - break;
888 - }
889 - let sibling = nextFiber.sibling;
890 - if (sibling !== null) {
891 - // Set the return pointer of the sibling to the work-in-progress fiber.
892 - sibling.return = nextFiber.return;
893 - nextFiber = sibling;
894 - break;
895 - }
896 - // No more siblings. Traverse up.
897 - nextFiber = nextFiber.return;
851 }
852 + // No more siblings. Traverse up.
853 + nextFiber = nextFiber.return;
854 }
900 - fiber = nextFiber;
855 }
856 + fiber = nextFiber;
857 }
858 +}
859
904 - function updateContextProvider(
905 - current,
906 - workInProgress,
907 - renderExpirationTime,
908 - ) {
909 - const providerType: ReactProviderType<any> = workInProgress.type;
910 - const context: ReactContext<any> = providerType._context;
860 +function updateContextProvider(current, workInProgress, renderExpirationTime) {
861 + const providerType: ReactProviderType<any> = workInProgress.type;
862 + const context: ReactContext<any> = providerType._context;
863
912 - const newProps = workInProgress.pendingProps;
913 - const oldProps = workInProgress.memoizedProps;
914 - let canBailOnProps = true;
864 + const newProps = workInProgress.pendingProps;
865 + const oldProps = workInProgress.memoizedProps;
866 + let canBailOnProps = true;
867
916 - if (hasLegacyContextChanged()) {
917 - canBailOnProps = false;
918 - // Normally we can bail out on props equality but if context has changed
919 - // we don't do the bailout and we have to reuse existing props instead.
920 - } else if (oldProps === newProps) {
921 - workInProgress.stateNode = 0;
922 - pushProvider(workInProgress);
923 - return bailoutOnAlreadyFinishedWork(current, workInProgress);
924 - }
868 + if (hasLegacyContextChanged()) {
869 + canBailOnProps = false;
870 + // Normally we can bail out on props equality but if context has changed
871 + // we don't do the bailout and we have to reuse existing props instead.
872 + } else if (oldProps === newProps) {
873 + workInProgress.stateNode = 0;
874 + pushProvider(workInProgress);
875 + return bailoutOnAlreadyFinishedWork(current, workInProgress);
876 + }
877
926 - const newValue = newProps.value;
927 - workInProgress.memoizedProps = newProps;
878 + const newValue = newProps.value;
879 + workInProgress.memoizedProps = newProps;
880
929 - if (__DEV__) {
930 - const providerPropTypes = workInProgress.type.propTypes;
931 -
932 - if (providerPropTypes) {
933 - checkPropTypes(
934 - providerPropTypes,
935 - newProps,
936 - 'prop',
937 - 'Context.Provider',
938 - getCurrentFiberStackAddendum,
939 - );
940 - }
881 + if (__DEV__) {
882 + const providerPropTypes = workInProgress.type.propTypes;
883 +
884 + if (providerPropTypes) {
885 + checkPropTypes(
886 + providerPropTypes,
887 + newProps,
888 + 'prop',
889 + 'Context.Provider',
890 + getCurrentFiberStackAddendum,
891 + );
892 }
893 + }
894
943 - let changedBits: number;
944 - if (oldProps === null) {
945 - // Initial render
946 - changedBits = MAX_SIGNED_31_BIT_INT;
895 + let changedBits: number;
896 + if (oldProps === null) {
897 + // Initial render
898 + changedBits = MAX_SIGNED_31_BIT_INT;
899 + } else {
900 + if (oldProps.value === newProps.value) {
901 + // No change. Bailout early if children are the same.
902 + if (oldProps.children === newProps.children && canBailOnProps) {
903 + workInProgress.stateNode = 0;
904 + pushProvider(workInProgress);
905 + return bailoutOnAlreadyFinishedWork(current, workInProgress);
906 + }
907 + changedBits = 0;
908 } else {
948 - if (oldProps.value === newProps.value) {
909 + const oldValue = oldProps.value;
910 + // Use Object.is to compare the new context value to the old value.
911 + // Inlined Object.is polyfill.
912 + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
913 + if (
914 + (oldValue === newValue &&
915 + (oldValue !== 0 || 1 / oldValue === 1 / newValue)) ||
916 + (oldValue !== oldValue && newValue !== newValue) // eslint-disable-line no-self-compare
917 + ) {
918 // No change. Bailout early if children are the same.
919 if (oldProps.children === newProps.children && canBailOnProps) {
920 workInProgress.stateNode = 0;
@@ -954,139 +923,118 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
923 }
924 changedBits = 0;
925 } else {
957 - const oldValue = oldProps.value;
958 - // Use Object.is to compare the new context value to the old value.
959 - // Inlined Object.is polyfill.
960 - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
961 - if (
962 - (oldValue === newValue &&
963 - (oldValue !== 0 || 1 / oldValue === 1 / newValue)) ||
964 - (oldValue !== oldValue && newValue !== newValue) // eslint-disable-line no-self-compare
965 - ) {
926 + changedBits =
927 + typeof context._calculateChangedBits === 'function'
928 + ? context._calculateChangedBits(oldValue, newValue)
929 + : MAX_SIGNED_31_BIT_INT;
930 + if (__DEV__) {
931 + warning(
932 + (changedBits & MAX_SIGNED_31_BIT_INT) === changedBits,
933 + 'calculateChangedBits: Expected the return value to be a ' +
934 + '31-bit integer. Instead received: %s',
935 + changedBits,
936 + );
937 + }
938 + changedBits |= 0;
939 +
940 + if (changedBits === 0) {
941 // No change. Bailout early if children are the same.
942 if (oldProps.children === newProps.children && canBailOnProps) {
943 workInProgress.stateNode = 0;
944 pushProvider(workInProgress);
945 return bailoutOnAlreadyFinishedWork(current, workInProgress);
946 }
972 - changedBits = 0;
947 } else {
974 - changedBits =
975 - typeof context._calculateChangedBits === 'function'
976 - ? context._calculateChangedBits(oldValue, newValue)
977 - : MAX_SIGNED_31_BIT_INT;
978 - if (__DEV__) {
979 - warning(
980 - (changedBits & MAX_SIGNED_31_BIT_INT) === changedBits,
981 - 'calculateChangedBits: Expected the return value to be a ' +
982 - '31-bit integer. Instead received: %s',
983 - changedBits,
984 - );
985 - }
986 - changedBits |= 0;
987 -
988 - if (changedBits === 0) {
989 - // No change. Bailout early if children are the same.
990 - if (oldProps.children === newProps.children && canBailOnProps) {
991 - workInProgress.stateNode = 0;
992 - pushProvider(workInProgress);
993 - return bailoutOnAlreadyFinishedWork(current, workInProgress);
994 - }
995 - } else {
996 - propagateContextChange(
997 - workInProgress,
998 - context,
999 - changedBits,
1000 - renderExpirationTime,
1001 - );
1002 - }
948 + propagateContextChange(
949 + workInProgress,
950 + context,
951 + changedBits,
952 + renderExpirationTime,
953 + );
954 }
955 }
956 }
1006 -
1007 - workInProgress.stateNode = changedBits;
1008 - pushProvider(workInProgress);
1009 -
1010 - const newChildren = newProps.children;
1011 - reconcileChildren(current, workInProgress, newChildren);
1012 - return workInProgress.child;
957 }
958
1015 - function updateContextConsumer(
1016 - current,
1017 - workInProgress,
1018 - renderExpirationTime,
1019 - ) {
1020 - const context: ReactContext<any> = workInProgress.type;
1021 - const newProps = workInProgress.pendingProps;
1022 - const oldProps = workInProgress.memoizedProps;
959 + workInProgress.stateNode = changedBits;
960 + pushProvider(workInProgress);
961
1024 - const newValue = getContextCurrentValue(context);
1025 - const changedBits = getContextChangedBits(context);
1026 -
1027 - if (hasLegacyContextChanged()) {
1028 - // Normally we can bail out on props equality but if context has changed
1029 - // we don't do the bailout and we have to reuse existing props instead.
1030 - } else if (changedBits === 0 && oldProps === newProps) {
1031 - return bailoutOnAlreadyFinishedWork(current, workInProgress);
1032 - }
1033 - workInProgress.memoizedProps = newProps;
962 + const newChildren = newProps.children;
963 + reconcileChildren(current, workInProgress, newChildren);
964 + return workInProgress.child;
965 +}
966
1035 - let observedBits = newProps.unstable_observedBits;
1036 - if (observedBits === undefined || observedBits === null) {
1037 - // Subscribe to all changes by default
1038 - observedBits = MAX_SIGNED_31_BIT_INT;
1039 - }
1040 - // Store the observedBits on the fiber's stateNode for quick access.
1041 - workInProgress.stateNode = observedBits;
967 +function updateContextConsumer(current, workInProgress, renderExpirationTime) {
968 + const context: ReactContext<any> = workInProgress.type;
969 + const newProps = workInProgress.pendingProps;
970 + const oldProps = workInProgress.memoizedProps;
971
1043 - if ((changedBits & observedBits) !== 0) {
1044 - // Context change propagation stops at matching consumers, for time-
1045 - // slicing. Continue the propagation here.
1046 - propagateContextChange(
1047 - workInProgress,
1048 - context,
1049 - changedBits,
1050 - renderExpirationTime,
1051 - );
1052 - } else if (oldProps === newProps) {
1053 - // Skip over a memoized parent with a bitmask bailout even
1054 - // if we began working on it because of a deeper matching child.
1055 - return bailoutOnAlreadyFinishedWork(current, workInProgress);
1056 - }
1057 - // There is no bailout on `children` equality because we expect people
1058 - // to often pass a bound method as a child, but it may reference
1059 - // `this.state` or `this.props` (and thus needs to re-render on `setState`).
972 + const newValue = getContextCurrentValue(context);
973 + const changedBits = getContextChangedBits(context);
974
1061 - const render = newProps.children;
975 + if (hasLegacyContextChanged()) {
976 + // Normally we can bail out on props equality but if context has changed
977 + // we don't do the bailout and we have to reuse existing props instead.
978 + } else if (changedBits === 0 && oldProps === newProps) {
979 + return bailoutOnAlreadyFinishedWork(current, workInProgress);
980 + }
981 + workInProgress.memoizedProps = newProps;
982
1063 - if (__DEV__) {
1064 - warning(
1065 - typeof render === 'function',
1066 - 'A context consumer was rendered with multiple children, or a child ' +
1067 - "that isn't a function. A context consumer expects a single child " +
1068 - 'that is a function. If you did pass a function, make sure there ' +
1069 - 'is no trailing or leading whitespace around it.',
1070 - );
1071 - }
983 + let observedBits = newProps.unstable_observedBits;
984 + if (observedBits === undefined || observedBits === null) {
985 + // Subscribe to all changes by default
986 + observedBits = MAX_SIGNED_31_BIT_INT;
987 + }
988 + // Store the observedBits on the fiber's stateNode for quick access.
989 + workInProgress.stateNode = observedBits;
990
1073 - let newChildren;
1074 - if (__DEV__) {
1075 - ReactCurrentOwner.current = workInProgress;
1076 - ReactDebugCurrentFiber.setCurrentPhase('render');
1077 - newChildren = render(newValue);
1078 - ReactDebugCurrentFiber.setCurrentPhase(null);
1079 - } else {
1080 - newChildren = render(newValue);
1081 - }
991 + if ((changedBits & observedBits) !== 0) {
992 + // Context change propagation stops at matching consumers, for time-
993 + // slicing. Continue the propagation here.
994 + propagateContextChange(
995 + workInProgress,
996 + context,
997 + changedBits,
998 + renderExpirationTime,
999 + );
1000 + } else if (oldProps === newProps) {
1001 + // Skip over a memoized parent with a bitmask bailout even
1002 + // if we began working on it because of a deeper matching child.
1003 + return bailoutOnAlreadyFinishedWork(current, workInProgress);
1004 + }
1005 + // There is no bailout on `children` equality because we expect people
1006 + // to often pass a bound method as a child, but it may reference
1007 + // `this.state` or `this.props` (and thus needs to re-render on `setState`).
1008 +
1009 + const render = newProps.children;
1010 +
1011 + if (__DEV__) {
1012 + warning(
1013 + typeof render === 'function',
1014 + 'A context consumer was rendered with multiple children, or a child ' +
1015 + "that isn't a function. A context consumer expects a single child " +
1016 + 'that is a function. If you did pass a function, make sure there ' +
1017 + 'is no trailing or leading whitespace around it.',
1018 + );
1019 + }
1020
1083 - // React DevTools reads this flag.
1084 - workInProgress.effectTag |= PerformedWork;
1085 - reconcileChildren(current, workInProgress, newChildren);
1086 - return workInProgress.child;
1021 + let newChildren;
1022 + if (__DEV__) {
1023 + ReactCurrentOwner.current = workInProgress;
1024 + ReactDebugCurrentFiber.setCurrentPhase('render');
1025 + newChildren = render(newValue);
1026 + ReactDebugCurrentFiber.setCurrentPhase(null);
1027 + } else {
1028 + newChildren = render(newValue);
1029 }
1030
1089 - /*
1031 + // React DevTools reads this flag.
1032 + workInProgress.effectTag |= PerformedWork;
1033 + reconcileChildren(current, workInProgress, newChildren);
1034 + return workInProgress.child;
1035 +}
1036 +
1037 +/*
1038 function reuseChildrenEffects(returnFiber : Fiber, firstChild : Fiber) {
1039 let child = firstChild;
1040 do {
@@ -1105,162 +1053,152 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
1053 }
1054 */
1055
1108 - function bailoutOnAlreadyFinishedWork(
1109 - current,
1110 - workInProgress: Fiber,
1111 - ): Fiber | null {
1112 - cancelWorkTimer(workInProgress);
1113 -
1114 - if (enableProfilerTimer) {
1115 - // Don't update "base" render times for bailouts.
1116 - stopBaseRenderTimerIfRunning();
1117 - }
1056 +function bailoutOnAlreadyFinishedWork(
1057 + current,
1058 + workInProgress: Fiber,
1059 +): Fiber | null {
1060 + cancelWorkTimer(workInProgress);
1061
1119 - // TODO: We should ideally be able to bail out early if the children have no
1120 - // more work to do. However, since we don't have a separation of this
1121 - // Fiber's priority and its children yet - we don't know without doing lots
1122 - // of the same work we do anyway. Once we have that separation we can just
1123 - // bail out here if the children has no more work at this priority level.
1124 - // if (workInProgress.priorityOfChildren <= priorityLevel) {
1125 - // // If there are side-effects in these children that have not yet been
1126 - // // committed we need to ensure that they get properly transferred up.
1127 - // if (current && current.child !== workInProgress.child) {
1128 - // reuseChildrenEffects(workInProgress, child);
1129 - // }
1130 - // return null;
1131 - // }
1132 -
1133 - cloneChildFibers(current, workInProgress);
1134 - return workInProgress.child;
1062 + if (enableProfilerTimer) {
1063 + // Don't update "base" render times for bailouts.
1064 + stopBaseRenderTimerIfRunning();
1065 }
1066
1137 - function bailoutOnLowPriority(current, workInProgress) {
1138 - cancelWorkTimer(workInProgress);
1067 + // TODO: We should ideally be able to bail out early if the children have no
1068 + // more work to do. However, since we don't have a separation of this
1069 + // Fiber's priority and its children yet - we don't know without doing lots
1070 + // of the same work we do anyway. Once we have that separation we can just
1071 + // bail out here if the children has no more work at this priority level.
1072 + // if (workInProgress.priorityOfChildren <= priorityLevel) {
1073 + // // If there are side-effects in these children that have not yet been
1074 + // // committed we need to ensure that they get properly transferred up.
1075 + // if (current && current.child !== workInProgress.child) {
1076 + // reuseChildrenEffects(workInProgress, child);
1077 + // }
1078 + // return null;
1079 + // }
1080 +
1081 + cloneChildFibers(current, workInProgress);
1082 + return workInProgress.child;
1083 +}
1084
1140 - if (enableProfilerTimer) {
1141 - // Don't update "base" render times for bailouts.
1142 - stopBaseRenderTimerIfRunning();
1143 - }
1085 +function bailoutOnLowPriority(current, workInProgress) {
1086 + cancelWorkTimer(workInProgress);
1087
1145 - // TODO: Handle HostComponent tags here as well and call pushHostContext()?
1146 - // See PR 8590 discussion for context
1147 - switch (workInProgress.tag) {
1148 - case HostRoot:
1149 - pushHostRootContext(workInProgress);
1150 - break;
1151 - case ClassComponent:
1152 - pushLegacyContextProvider(workInProgress);
1153 - break;
1154 - case HostPortal:
1155 - pushHostContainer(
1156 - workInProgress,
1157 - workInProgress.stateNode.containerInfo,
1158 - );
1159 - break;
1160 - case ContextProvider:
1161 - pushProvider(workInProgress);
1162 - break;
1163 - case Profiler:
1164 - if (enableProfilerTimer) {
1165 - markActualRenderTimeStarted(workInProgress);
1166 - }
1167 - break;
1168 - }
1169 - // TODO: What if this is currently in progress?
1170 - // How can that happen? How is this not being cloned?
1171 - return null;
1088 + if (enableProfilerTimer) {
1089 + // Don't update "base" render times for bailouts.
1090 + stopBaseRenderTimerIfRunning();
1091 }
1092
1174 - // TODO: Delete memoizeProps/State and move to reconcile/bailout instead
1175 - function memoizeProps(workInProgress: Fiber, nextProps: any) {
1176 - workInProgress.memoizedProps = nextProps;
1093 + // TODO: Handle HostComponent tags here as well and call pushHostContext()?
1094 + // See PR 8590 discussion for context
1095 + switch (workInProgress.tag) {
1096 + case HostRoot:
1097 + pushHostRootContext(workInProgress);
1098 + break;
1099 + case ClassComponent:
1100 + pushLegacyContextProvider(workInProgress);
1101 + break;
1102 + case HostPortal:
1103 + pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
1104 + break;
1105 + case ContextProvider:
1106 + pushProvider(workInProgress);
1107 + break;
1108 + case Profiler:
1109 + if (enableProfilerTimer) {
1110 + markActualRenderTimeStarted(workInProgress);
1111 + }
1112 + break;
1113 }
1114 + // TODO: What if this is currently in progress?
1115 + // How can that happen? How is this not being cloned?
1116 + return null;
1117 +}
1118
1179 - function memoizeState(workInProgress: Fiber, nextState: any) {
1180 - workInProgress.memoizedState = nextState;
1181 - // Don't reset the updateQueue, in case there are pending updates. Resetting
1182 - // is handled by processUpdateQueue.
1183 - }
1119 +// TODO: Delete memoizeProps/State and move to reconcile/bailout instead
1120 +function memoizeProps(workInProgress: Fiber, nextProps: any) {
1121 + workInProgress.memoizedProps = nextProps;
1122 +}
1123
1185 - function beginWork(
1186 - current: Fiber | null,
1187 - workInProgress: Fiber,
1188 - renderExpirationTime: ExpirationTime,
1189 - ): Fiber | null {
1190 - if (
1191 - workInProgress.expirationTime === NoWork ||
1192 - workInProgress.expirationTime > renderExpirationTime
1193 - ) {
1194 - return bailoutOnLowPriority(current, workInProgress);
1195 - }
1124 +function memoizeState(workInProgress: Fiber, nextState: any) {
1125 + workInProgress.memoizedState = nextState;
1126 + // Don't reset the updateQueue, in case there are pending updates. Resetting
1127 + // is handled by processUpdateQueue.
1128 +}
1129
1197 - switch (workInProgress.tag) {
1198 - case IndeterminateComponent:
1199 - return mountIndeterminateComponent(
1200 - current,
1201 - workInProgress,
1202 - renderExpirationTime,
1203 - );
1204 - case FunctionalComponent:
1205 - return updateFunctionalComponent(current, workInProgress);
1206 - case ClassComponent:
1207 - return updateClassComponent(
1208 - current,
1209 - workInProgress,
1210 - renderExpirationTime,
1211 - );
1212 - case HostRoot:
1213 - return updateHostRoot(current, workInProgress, renderExpirationTime);
1214 - case HostComponent:
1215 - return updateHostComponent(
1216 - current,
1217 - workInProgress,
1218 - renderExpirationTime,
1219 - );
1220 - case HostText:
1221 - return updateHostText(current, workInProgress);
1222 - case TimeoutComponent:
1223 - return updateTimeoutComponent(
1224 - current,
1225 - workInProgress,
1226 - renderExpirationTime,
1227 - );
1228 - case HostPortal:
1229 - return updatePortalComponent(
1230 - current,
1231 - workInProgress,
1232 - renderExpirationTime,
1233 - );
1234 - case ForwardRef:
1235 - return updateForwardRef(current, workInProgress);
1236 - case Fragment:
1237 - return updateFragment(current, workInProgress);
1238 - case Mode:
1239 - return updateMode(current, workInProgress);
1240 - case Profiler:
1241 - return updateProfiler(current, workInProgress);
1242 - case ContextProvider:
1243 - return updateContextProvider(
1244 - current,
1245 - workInProgress,
1246 - renderExpirationTime,
1247 - );
1248 - case ContextConsumer:
1249 - return updateContextConsumer(
1250 - current,
1251 - workInProgress,
1252 - renderExpirationTime,
1253 - );
1254 - default:
1255 - invariant(
1256 - false,
1257 - 'Unknown unit of work tag. This error is likely caused by a bug in ' +
1258 - 'React. Please file an issue.',
1259 - );
1260 - }
1130 +function beginWork(
1131 + current: Fiber | null,
1132 + workInProgress: Fiber,
1133 + renderExpirationTime: ExpirationTime,
1134 +): Fiber | null {
1135 + if (
1136 + workInProgress.expirationTime === NoWork ||
1137 + workInProgress.expirationTime > renderExpirationTime
1138 + ) {
1139 + return bailoutOnLowPriority(current, workInProgress);
1140 }
1141
1263 - return {
1264 - beginWork,
1265 - };
1142 + switch (workInProgress.tag) {
1143 + case IndeterminateComponent:
1144 + return mountIndeterminateComponent(
1145 + current,
1146 + workInProgress,
1147 + renderExpirationTime,
1148 + );
1149 + case FunctionalComponent:
1150 + return updateFunctionalComponent(current, workInProgress);
1151 + case ClassComponent:
1152 + return updateClassComponent(
1153 + current,
1154 + workInProgress,
1155 + renderExpirationTime,
1156 + );
1157 + case HostRoot:
1158 + return updateHostRoot(current, workInProgress, renderExpirationTime);
1159 + case HostComponent:
1160 + return updateHostComponent(current, workInProgress, renderExpirationTime);
1161 + case HostText:
1162 + return updateHostText(current, workInProgress);
1163 + case TimeoutComponent:
1164 + return updateTimeoutComponent(
1165 + current,
1166 + workInProgress,
1167 + renderExpirationTime,
1168 + );
1169 + case HostPortal:
1170 + return updatePortalComponent(
1171 + current,
1172 + workInProgress,
1173 + renderExpirationTime,
1174 + );
1175 + case ForwardRef:
1176 + return updateForwardRef(current, workInProgress);
1177 + case Fragment:
1178 + return updateFragment(current, workInProgress);
1179 + case Mode:
1180 + return updateMode(current, workInProgress);
1181 + case Profiler:
1182 + return updateProfiler(current, workInProgress);
1183 + case ContextProvider:
1184 + return updateContextProvider(
1185 + current,
1186 + workInProgress,
1187 + renderExpirationTime,
1188 + );
1189 + case ContextConsumer:
1190 + return updateContextConsumer(
1191 + current,
1192 + workInProgress,
1193 + renderExpirationTime,
1194 + );
1195 + default:
1196 + invariant(
1197 + false,
1198 + 'Unknown unit of work tag. This error is likely caused by a bug in ' +
1199 + 'React. Please file an issue.',
1200 + );
1201 + }
1202 }
1203 +
1204 +export {beginWork};
packages/react-reconciler/src/ReactFiberClassComponent.js
+731 -749
@@ -9,7 +9,6 @@
9
10 import type {Fiber} from './ReactFiber';
11 import type {ExpirationTime} from './ReactFiberExpirationTime';
12 -import type {LegacyContext} from './ReactFiberContext';
12
13 import {Update, Snapshot} from 'shared/ReactTypeOfSideEffect';
14 import {
@@ -39,6 +38,18 @@ import {
38 ForceUpdate,
39 } from './ReactUpdateQueue';
40 import {NoWork} from './ReactFiberExpirationTime';
41 +import {
42 + cacheContext,
43 + getMaskedContext,
44 + getUnmaskedContext,
45 + isContextConsumer,
46 + hasContextChanged,
47 +} from './ReactFiberContext';
48 +import {
49 + recalculateCurrentTime,
50 + computeExpirationForFiber,
51 + scheduleWork,
52 +} from './ReactFiberScheduler';
53
54 const fakeInternalInstance = {};
55 const isArray = Array.isArray;
@@ -152,545 +163,551 @@ export function applyDerivedStateFromProps(
163 }
164 }
165
155 -export default function(
156 - legacyContext: LegacyContext,
157 - scheduleWork: (fiber: Fiber, expirationTime: ExpirationTime) => void,
158 - computeExpirationForFiber: (
159 - currentTime: ExpirationTime,
160 - fiber: Fiber,
161 - ) => ExpirationTime,
162 - memoizeProps: (workInProgress: Fiber, props: any) => void,
163 - memoizeState: (workInProgress: Fiber, state: any) => void,
164 - recalculateCurrentTime: () => ExpirationTime,
165 -) {
166 - const {
167 - cacheContext,
168 - getMaskedContext,
169 - getUnmaskedContext,
170 - isContextConsumer,
171 - hasContextChanged,
172 - } = legacyContext;
173 -
174 - const classComponentUpdater = {
175 - isMounted,
176 - enqueueSetState(inst, payload, callback) {
177 - const fiber = ReactInstanceMap.get(inst);
178 - const currentTime = recalculateCurrentTime();
179 - const expirationTime = computeExpirationForFiber(currentTime, fiber);
180 -
181 - const update = createUpdate(expirationTime);
182 - update.payload = payload;
183 - if (callback !== undefined && callback !== null) {
184 - if (__DEV__) {
185 - warnOnInvalidCallback(callback, 'setState');
186 - }
187 - update.callback = callback;
188 - }
166 +const classComponentUpdater = {
167 + isMounted,
168 + enqueueSetState(inst, payload, callback) {
169 + const fiber = ReactInstanceMap.get(inst);
170 + const currentTime = recalculateCurrentTime();
171 + const expirationTime = computeExpirationForFiber(currentTime, fiber);
172
190 - enqueueUpdate(fiber, update, expirationTime);
191 - scheduleWork(fiber, expirationTime);
192 - },
193 - enqueueReplaceState(inst, payload, callback) {
194 - const fiber = ReactInstanceMap.get(inst);
195 - const currentTime = recalculateCurrentTime();
196 - const expirationTime = computeExpirationForFiber(currentTime, fiber);
197 -
198 - const update = createUpdate(expirationTime);
199 - update.tag = ReplaceState;
200 - update.payload = payload;
201 -
202 - if (callback !== undefined && callback !== null) {
203 - if (__DEV__) {
204 - warnOnInvalidCallback(callback, 'replaceState');
205 - }
206 - update.callback = callback;
173 + const update = createUpdate(expirationTime);
174 + update.payload = payload;
175 + if (callback !== undefined && callback !== null) {
176 + if (__DEV__) {
177 + warnOnInvalidCallback(callback, 'setState');
178 }
179 + update.callback = callback;
180 + }
181
209 - enqueueUpdate(fiber, update, expirationTime);
210 - scheduleWork(fiber, expirationTime);
211 - },
212 - enqueueForceUpdate(inst, callback) {
213 - const fiber = ReactInstanceMap.get(inst);
214 - const currentTime = recalculateCurrentTime();
215 - const expirationTime = computeExpirationForFiber(currentTime, fiber);
182 + enqueueUpdate(fiber, update, expirationTime);
183 + scheduleWork(fiber, expirationTime);
184 + },
185 + enqueueReplaceState(inst, payload, callback) {
186 + const fiber = ReactInstanceMap.get(inst);
187 + const currentTime = recalculateCurrentTime();
188 + const expirationTime = computeExpirationForFiber(currentTime, fiber);
189
217 - const update = createUpdate(expirationTime);
218 - update.tag = ForceUpdate;
190 + const update = createUpdate(expirationTime);
191 + update.tag = ReplaceState;
192 + update.payload = payload;
193
220 - if (callback !== undefined && callback !== null) {
221 - if (__DEV__) {
222 - warnOnInvalidCallback(callback, 'forceUpdate');
223 - }
224 - update.callback = callback;
194 + if (callback !== undefined && callback !== null) {
195 + if (__DEV__) {
196 + warnOnInvalidCallback(callback, 'replaceState');
197 }
198 + update.callback = callback;
199 + }
200
227 - enqueueUpdate(fiber, update, expirationTime);
228 - scheduleWork(fiber, expirationTime);
229 - },
230 - };
201 + enqueueUpdate(fiber, update, expirationTime);
202 + scheduleWork(fiber, expirationTime);
203 + },
204 + enqueueForceUpdate(inst, callback) {
205 + const fiber = ReactInstanceMap.get(inst);
206 + const currentTime = recalculateCurrentTime();
207 + const expirationTime = computeExpirationForFiber(currentTime, fiber);
208
232 - function checkShouldComponentUpdate(
233 - workInProgress,
234 - oldProps,
235 - newProps,
236 - oldState,
237 - newState,
238 - newContext,
239 - ) {
240 - const instance = workInProgress.stateNode;
241 - const ctor = workInProgress.type;
242 - if (typeof instance.shouldComponentUpdate === 'function') {
243 - startPhaseTimer(workInProgress, 'shouldComponentUpdate');
244 - const shouldUpdate = instance.shouldComponentUpdate(
245 - newProps,
246 - newState,
247 - newContext,
248 - );
249 - stopPhaseTimer();
209 + const update = createUpdate(expirationTime);
210 + update.tag = ForceUpdate;
211
212 + if (callback !== undefined && callback !== null) {
213 if (__DEV__) {
252 - warning(
253 - shouldUpdate !== undefined,
254 - '%s.shouldComponentUpdate(): Returned undefined instead of a ' +
255 - 'boolean value. Make sure to return true or false.',
256 - getComponentName(workInProgress) || 'Component',
257 - );
214 + warnOnInvalidCallback(callback, 'forceUpdate');
215 }
259 -
260 - return shouldUpdate;
216 + update.callback = callback;
217 }
218
263 - if (ctor.prototype && ctor.prototype.isPureReactComponent) {
264 - return (
265 - !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
219 + enqueueUpdate(fiber, update, expirationTime);
220 + scheduleWork(fiber, expirationTime);
221 + },
222 +};
223 +
224 +function checkShouldComponentUpdate(
225 + workInProgress,
226 + oldProps,
227 + newProps,
228 + oldState,
229 + newState,
230 + newContext,
231 +) {
232 + const instance = workInProgress.stateNode;
233 + const ctor = workInProgress.type;
234 + if (typeof instance.shouldComponentUpdate === 'function') {
235 + startPhaseTimer(workInProgress, 'shouldComponentUpdate');
236 + const shouldUpdate = instance.shouldComponentUpdate(
237 + newProps,
238 + newState,
239 + newContext,
240 + );
241 + stopPhaseTimer();
242 +
243 + if (__DEV__) {
244 + warning(
245 + shouldUpdate !== undefined,
246 + '%s.shouldComponentUpdate(): Returned undefined instead of a ' +
247 + 'boolean value. Make sure to return true or false.',
248 + getComponentName(workInProgress) || 'Component',
249 );
250 }
251
269 - return true;
252 + return shouldUpdate;
253 }
254
272 - function checkClassInstance(workInProgress: Fiber) {
273 - const instance = workInProgress.stateNode;
274 - const type = workInProgress.type;
275 - if (__DEV__) {
276 - const name = getComponentName(workInProgress) || 'Component';
277 - const renderPresent = instance.render;
255 + if (ctor.prototype && ctor.prototype.isPureReactComponent) {
256 + return (
257 + !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
258 + );
259 + }
260
279 - if (!renderPresent) {
280 - if (type.prototype && typeof type.prototype.render === 'function') {
281 - warning(
282 - false,
283 - '%s(...): No `render` method found on the returned component ' +
284 - 'instance: did you accidentally return an object from the constructor?',
285 - name,
286 - );
287 - } else {
288 - warning(
289 - false,
290 - '%s(...): No `render` method found on the returned component ' +
291 - 'instance: you may have forgotten to define `render`.',
292 - name,
293 - );
294 - }
295 - }
261 + return true;
262 +}
263
297 - const noGetInitialStateOnES6 =
298 - !instance.getInitialState ||
299 - instance.getInitialState.isReactClassApproved ||
300 - instance.state;
301 - warning(
302 - noGetInitialStateOnES6,
303 - 'getInitialState was defined on %s, a plain JavaScript class. ' +
304 - 'This is only supported for classes created using React.createClass. ' +
305 - 'Did you mean to define a state property instead?',
306 - name,
307 - );
308 - const noGetDefaultPropsOnES6 =
309 - !instance.getDefaultProps ||
310 - instance.getDefaultProps.isReactClassApproved;
311 - warning(
312 - noGetDefaultPropsOnES6,
313 - 'getDefaultProps was defined on %s, a plain JavaScript class. ' +
314 - 'This is only supported for classes created using React.createClass. ' +
315 - 'Use a static property to define defaultProps instead.',
316 - name,
317 - );
318 - const noInstancePropTypes = !instance.propTypes;
319 - warning(
320 - noInstancePropTypes,
321 - 'propTypes was defined as an instance property on %s. Use a static ' +
322 - 'property to define propTypes instead.',
323 - name,
324 - );
325 - const noInstanceContextTypes = !instance.contextTypes;
326 - warning(
327 - noInstanceContextTypes,
328 - 'contextTypes was defined as an instance property on %s. Use a static ' +
329 - 'property to define contextTypes instead.',
330 - name,
331 - );
332 - const noComponentShouldUpdate =
333 - typeof instance.componentShouldUpdate !== 'function';
334 - warning(
335 - noComponentShouldUpdate,
336 - '%s has a method called ' +
337 - 'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' +
338 - 'The name is phrased as a question because the function is ' +
339 - 'expected to return a value.',
340 - name,
341 - );
342 - if (
343 - type.prototype &&
344 - type.prototype.isPureReactComponent &&
345 - typeof instance.shouldComponentUpdate !== 'undefined'
346 - ) {
264 +function checkClassInstance(workInProgress: Fiber) {
265 + const instance = workInProgress.stateNode;
266 + const type = workInProgress.type;
267 + if (__DEV__) {
268 + const name = getComponentName(workInProgress) || 'Component';
269 + const renderPresent = instance.render;
270 +
271 + if (!renderPresent) {
272 + if (type.prototype && typeof type.prototype.render === 'function') {
273 warning(
274 false,
349 - '%s has a method called shouldComponentUpdate(). ' +
350 - 'shouldComponentUpdate should not be used when extending React.PureComponent. ' +
351 - 'Please extend React.Component if shouldComponentUpdate is used.',
352 - getComponentName(workInProgress) || 'A pure component',
275 + '%s(...): No `render` method found on the returned component ' +
276 + 'instance: did you accidentally return an object from the constructor?',
277 + name,
278 );
354 - }
355 - const noComponentDidUnmount =
356 - typeof instance.componentDidUnmount !== 'function';
357 - warning(
358 - noComponentDidUnmount,
359 - '%s has a method called ' +
360 - 'componentDidUnmount(). But there is no such lifecycle method. ' +
361 - 'Did you mean componentWillUnmount()?',
362 - name,
363 - );
364 - const noComponentDidReceiveProps =
365 - typeof instance.componentDidReceiveProps !== 'function';
366 - warning(
367 - noComponentDidReceiveProps,
368 - '%s has a method called ' +
369 - 'componentDidReceiveProps(). But there is no such lifecycle method. ' +
370 - 'If you meant to update the state in response to changing props, ' +
371 - 'use componentWillReceiveProps(). If you meant to fetch data or ' +
372 - 'run side-effects or mutations after React has updated the UI, use componentDidUpdate().',
373 - name,
374 - );
375 - const noComponentWillRecieveProps =
376 - typeof instance.componentWillRecieveProps !== 'function';
377 - warning(
378 - noComponentWillRecieveProps,
379 - '%s has a method called ' +
380 - 'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?',
381 - name,
382 - );
383 - const noUnsafeComponentWillRecieveProps =
384 - typeof instance.UNSAFE_componentWillRecieveProps !== 'function';
385 - warning(
386 - noUnsafeComponentWillRecieveProps,
387 - '%s has a method called ' +
388 - 'UNSAFE_componentWillRecieveProps(). Did you mean UNSAFE_componentWillReceiveProps()?',
389 - name,
390 - );
391 - const hasMutatedProps = instance.props !== workInProgress.pendingProps;
392 - warning(
393 - instance.props === undefined || !hasMutatedProps,
394 - '%s(...): When calling super() in `%s`, make sure to pass ' +
395 - "up the same props that your component's constructor was passed.",
396 - name,
397 - name,
398 - );
399 - const noInstanceDefaultProps = !instance.defaultProps;
400 - warning(
401 - noInstanceDefaultProps,
402 - 'Setting defaultProps as an instance property on %s is not supported and will be ignored.' +
403 - ' Instead, define defaultProps as a static property on %s.',
404 - name,
405 - name,
406 - );
407 -
408 - if (
409 - typeof instance.getSnapshotBeforeUpdate === 'function' &&
410 - typeof instance.componentDidUpdate !== 'function' &&
411 - !didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate.has(type)
412 - ) {
413 - didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate.add(type);
279 + } else {
280 warning(
281 false,
416 - '%s: getSnapshotBeforeUpdate() should be used with componentDidUpdate(). ' +
417 - 'This component defines getSnapshotBeforeUpdate() only.',
418 - getComponentName(workInProgress),
282 + '%s(...): No `render` method found on the returned component ' +
283 + 'instance: you may have forgotten to define `render`.',
284 + name,
285 );
286 }
287 + }
288
422 - const noInstanceGetDerivedStateFromProps =
423 - typeof instance.getDerivedStateFromProps !== 'function';
289 + const noGetInitialStateOnES6 =
290 + !instance.getInitialState ||
291 + instance.getInitialState.isReactClassApproved ||
292 + instance.state;
293 + warning(
294 + noGetInitialStateOnES6,
295 + 'getInitialState was defined on %s, a plain JavaScript class. ' +
296 + 'This is only supported for classes created using React.createClass. ' +
297 + 'Did you mean to define a state property instead?',
298 + name,
299 + );
300 + const noGetDefaultPropsOnES6 =
301 + !instance.getDefaultProps ||
302 + instance.getDefaultProps.isReactClassApproved;
303 + warning(
304 + noGetDefaultPropsOnES6,
305 + 'getDefaultProps was defined on %s, a plain JavaScript class. ' +
306 + 'This is only supported for classes created using React.createClass. ' +
307 + 'Use a static property to define defaultProps instead.',
308 + name,
309 + );
310 + const noInstancePropTypes = !instance.propTypes;
311 + warning(
312 + noInstancePropTypes,
313 + 'propTypes was defined as an instance property on %s. Use a static ' +
314 + 'property to define propTypes instead.',
315 + name,
316 + );
317 + const noInstanceContextTypes = !instance.contextTypes;
318 + warning(
319 + noInstanceContextTypes,
320 + 'contextTypes was defined as an instance property on %s. Use a static ' +
321 + 'property to define contextTypes instead.',
322 + name,
323 + );
324 + const noComponentShouldUpdate =
325 + typeof instance.componentShouldUpdate !== 'function';
326 + warning(
327 + noComponentShouldUpdate,
328 + '%s has a method called ' +
329 + 'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' +
330 + 'The name is phrased as a question because the function is ' +
331 + 'expected to return a value.',
332 + name,
333 + );
334 + if (
335 + type.prototype &&
336 + type.prototype.isPureReactComponent &&
337 + typeof instance.shouldComponentUpdate !== 'undefined'
338 + ) {
339 warning(
425 - noInstanceGetDerivedStateFromProps,
426 - '%s: getDerivedStateFromProps() is defined as an instance method ' +
427 - 'and will be ignored. Instead, declare it as a static method.',
428 - name,
340 + false,
341 + '%s has a method called shouldComponentUpdate(). ' +
342 + 'shouldComponentUpdate should not be used when extending React.PureComponent. ' +
343 + 'Please extend React.Component if shouldComponentUpdate is used.',
344 + getComponentName(workInProgress) || 'A pure component',
345 );
430 - const noInstanceGetDerivedStateFromCatch =
431 - typeof instance.getDerivedStateFromCatch !== 'function';
346 + }
347 + const noComponentDidUnmount =
348 + typeof instance.componentDidUnmount !== 'function';
349 + warning(
350 + noComponentDidUnmount,
351 + '%s has a method called ' +
352 + 'componentDidUnmount(). But there is no such lifecycle method. ' +
353 + 'Did you mean componentWillUnmount()?',
354 + name,
355 + );
356 + const noComponentDidReceiveProps =
357 + typeof instance.componentDidReceiveProps !== 'function';
358 + warning(
359 + noComponentDidReceiveProps,
360 + '%s has a method called ' +
361 + 'componentDidReceiveProps(). But there is no such lifecycle method. ' +
362 + 'If you meant to update the state in response to changing props, ' +
363 + 'use componentWillReceiveProps(). If you meant to fetch data or ' +
364 + 'run side-effects or mutations after React has updated the UI, use componentDidUpdate().',
365 + name,
366 + );
367 + const noComponentWillRecieveProps =
368 + typeof instance.componentWillRecieveProps !== 'function';
369 + warning(
370 + noComponentWillRecieveProps,
371 + '%s has a method called ' +
372 + 'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?',
373 + name,
374 + );
375 + const noUnsafeComponentWillRecieveProps =
376 + typeof instance.UNSAFE_componentWillRecieveProps !== 'function';
377 + warning(
378 + noUnsafeComponentWillRecieveProps,
379 + '%s has a method called ' +
380 + 'UNSAFE_componentWillRecieveProps(). Did you mean UNSAFE_componentWillReceiveProps()?',
381 + name,
382 + );
383 + const hasMutatedProps = instance.props !== workInProgress.pendingProps;
384 + warning(
385 + instance.props === undefined || !hasMutatedProps,
386 + '%s(...): When calling super() in `%s`, make sure to pass ' +
387 + "up the same props that your component's constructor was passed.",
388 + name,
389 + name,
390 + );
391 + const noInstanceDefaultProps = !instance.defaultProps;
392 + warning(
393 + noInstanceDefaultProps,
394 + 'Setting defaultProps as an instance property on %s is not supported and will be ignored.' +
395 + ' Instead, define defaultProps as a static property on %s.',
396 + name,
397 + name,
398 + );
399 +
400 + if (
401 + typeof instance.getSnapshotBeforeUpdate === 'function' &&
402 + typeof instance.componentDidUpdate !== 'function' &&
403 + !didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate.has(type)
404 + ) {
405 + didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate.add(type);
406 warning(
433 - noInstanceGetDerivedStateFromCatch,
434 - '%s: getDerivedStateFromCatch() is defined as an instance method ' +
435 - 'and will be ignored. Instead, declare it as a static method.',
436 - name,
407 + false,
408 + '%s: getSnapshotBeforeUpdate() should be used with componentDidUpdate(). ' +
409 + 'This component defines getSnapshotBeforeUpdate() only.',
410 + getComponentName(workInProgress),
411 );
438 - const noStaticGetSnapshotBeforeUpdate =
439 - typeof type.getSnapshotBeforeUpdate !== 'function';
412 + }
413 +
414 + const noInstanceGetDerivedStateFromProps =
415 + typeof instance.getDerivedStateFromProps !== 'function';
416 + warning(
417 + noInstanceGetDerivedStateFromProps,
418 + '%s: getDerivedStateFromProps() is defined as an instance method ' +
419 + 'and will be ignored. Instead, declare it as a static method.',
420 + name,
421 + );
422 + const noInstanceGetDerivedStateFromCatch =
423 + typeof instance.getDerivedStateFromCatch !== 'function';
424 + warning(
425 + noInstanceGetDerivedStateFromCatch,
426 + '%s: getDerivedStateFromCatch() is defined as an instance method ' +
427 + 'and will be ignored. Instead, declare it as a static method.',
428 + name,
429 + );
430 + const noStaticGetSnapshotBeforeUpdate =
431 + typeof type.getSnapshotBeforeUpdate !== 'function';
432 + warning(
433 + noStaticGetSnapshotBeforeUpdate,
434 + '%s: getSnapshotBeforeUpdate() is defined as a static method ' +
435 + 'and will be ignored. Instead, declare it as an instance method.',
436 + name,
437 + );
438 + const state = instance.state;
439 + if (state && (typeof state !== 'object' || isArray(state))) {
440 + warning(false, '%s.state: must be set to an object or null', name);
441 + }
442 + if (typeof instance.getChildContext === 'function') {
443 warning(
441 - noStaticGetSnapshotBeforeUpdate,
442 - '%s: getSnapshotBeforeUpdate() is defined as a static method ' +
443 - 'and will be ignored. Instead, declare it as an instance method.',
444 + typeof type.childContextTypes === 'object',
445 + '%s.getChildContext(): childContextTypes must be defined in order to ' +
446 + 'use getChildContext().',
447 name,
448 );
446 - const state = instance.state;
447 - if (state && (typeof state !== 'object' || isArray(state))) {
448 - warning(false, '%s.state: must be set to an object or null', name);
449 - }
450 - if (typeof instance.getChildContext === 'function') {
451 - warning(
452 - typeof type.childContextTypes === 'object',
453 - '%s.getChildContext(): childContextTypes must be defined in order to ' +
454 - 'use getChildContext().',
455 - name,
456 - );
457 - }
449 }
450 }
451 +}
452
461 - function adoptClassInstance(workInProgress: Fiber, instance: any): void {
462 - instance.updater = classComponentUpdater;
463 - workInProgress.stateNode = instance;
464 - // The instance needs access to the fiber so that it can schedule updates
465 - ReactInstanceMap.set(instance, workInProgress);
466 - if (__DEV__) {
467 - instance._reactInternalInstance = fakeInternalInstance;
453 +function adoptClassInstance(workInProgress: Fiber, instance: any): void {
454 + instance.updater = classComponentUpdater;
455 + workInProgress.stateNode = instance;
456 + // The instance needs access to the fiber so that it can schedule updates
457 + ReactInstanceMap.set(instance, workInProgress);
458 + if (__DEV__) {
459 + instance._reactInternalInstance = fakeInternalInstance;
460 + }
461 +}
462 +
463 +function constructClassInstance(
464 + workInProgress: Fiber,
465 + props: any,
466 + renderExpirationTime: ExpirationTime,
467 +): any {
468 + const ctor = workInProgress.type;
469 + const unmaskedContext = getUnmaskedContext(workInProgress);
470 + const needsContext = isContextConsumer(workInProgress);
471 + const context = needsContext
472 + ? getMaskedContext(workInProgress, unmaskedContext)
473 + : emptyObject;
474 +
475 + // Instantiate twice to help detect side-effects.
476 + if (__DEV__) {
477 + if (
478 + debugRenderPhaseSideEffects ||
479 + (debugRenderPhaseSideEffectsForStrictMode &&
480 + workInProgress.mode & StrictMode)
481 + ) {
482 + new ctor(props, context); // eslint-disable-line no-new
483 }
484 }
485
471 - function constructClassInstance(
472 - workInProgress: Fiber,
473 - props: any,
474 - renderExpirationTime: ExpirationTime,
475 - ): any {
476 - const ctor = workInProgress.type;
477 - const unmaskedContext = getUnmaskedContext(workInProgress);
478 - const needsContext = isContextConsumer(workInProgress);
479 - const context = needsContext
480 - ? getMaskedContext(workInProgress, unmaskedContext)
481 - : emptyObject;
482 -
483 - // Instantiate twice to help detect side-effects.
484 - if (__DEV__) {
485 - if (
486 - debugRenderPhaseSideEffects ||
487 - (debugRenderPhaseSideEffectsForStrictMode &&
488 - workInProgress.mode & StrictMode)
489 - ) {
490 - new ctor(props, context); // eslint-disable-line no-new
486 + const instance = new ctor(props, context);
487 + const state = (workInProgress.memoizedState =
488 + instance.state !== null && instance.state !== undefined
489 + ? instance.state
490 + : null);
491 + adoptClassInstance(workInProgress, instance);
492 +
493 + if (__DEV__) {
494 + if (typeof ctor.getDerivedStateFromProps === 'function' && state === null) {
495 + const componentName = getComponentName(workInProgress) || 'Component';
496 + if (!didWarnAboutUninitializedState.has(componentName)) {
497 + didWarnAboutUninitializedState.add(componentName);
498 + warning(
499 + false,
500 + '%s: Did not properly initialize state during construction. ' +
501 + 'Expected state to be an object, but it was %s.',
502 + componentName,
503 + instance.state === null ? 'null' : 'undefined',
504 + );
505 }
506 }
507
494 - const instance = new ctor(props, context);
495 - const state = (workInProgress.memoizedState =
496 - instance.state !== null && instance.state !== undefined
497 - ? instance.state
498 - : null);
499 - adoptClassInstance(workInProgress, instance);
500 -
501 - if (__DEV__) {
508 + // If new component APIs are defined, "unsafe" lifecycles won't be called.
509 + // Warn about these lifecycles if they are present.
510 + // Don't warn about react-lifecycles-compat polyfilled methods though.
511 + if (
512 + typeof ctor.getDerivedStateFromProps === 'function' ||
513 + typeof instance.getSnapshotBeforeUpdate === 'function'
514 + ) {
515 + let foundWillMountName = null;
516 + let foundWillReceivePropsName = null;
517 + let foundWillUpdateName = null;
518 + if (
519 + typeof instance.componentWillMount === 'function' &&
520 + instance.componentWillMount.__suppressDeprecationWarning !== true
521 + ) {
522 + foundWillMountName = 'componentWillMount';
523 + } else if (typeof instance.UNSAFE_componentWillMount === 'function') {
524 + foundWillMountName = 'UNSAFE_componentWillMount';
525 + }
526 + if (
527 + typeof instance.componentWillReceiveProps === 'function' &&
528 + instance.componentWillReceiveProps.__suppressDeprecationWarning !== true
529 + ) {
530 + foundWillReceivePropsName = 'componentWillReceiveProps';
531 + } else if (
532 + typeof instance.UNSAFE_componentWillReceiveProps === 'function'
533 + ) {
534 + foundWillReceivePropsName = 'UNSAFE_componentWillReceiveProps';
535 + }
536 if (
503 - typeof ctor.getDerivedStateFromProps === 'function' &&
504 - state === null
537 + typeof instance.componentWillUpdate === 'function' &&
538 + instance.componentWillUpdate.__suppressDeprecationWarning !== true
539 + ) {
540 + foundWillUpdateName = 'componentWillUpdate';
541 + } else if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
542 + foundWillUpdateName = 'UNSAFE_componentWillUpdate';
543 + }
544 + if (
545 + foundWillMountName !== null ||
546 + foundWillReceivePropsName !== null ||
547 + foundWillUpdateName !== null
548 ) {
549 const componentName = getComponentName(workInProgress) || 'Component';
507 - if (!didWarnAboutUninitializedState.has(componentName)) {
508 - didWarnAboutUninitializedState.add(componentName);
550 + const newApiName =
551 + typeof ctor.getDerivedStateFromProps === 'function'
552 + ? 'getDerivedStateFromProps()'
553 + : 'getSnapshotBeforeUpdate()';
554 + if (!didWarnAboutLegacyLifecyclesAndDerivedState.has(componentName)) {
555 + didWarnAboutLegacyLifecyclesAndDerivedState.add(componentName);
556 warning(
557 false,
511 - '%s: Did not properly initialize state during construction. ' +
512 - 'Expected state to be an object, but it was %s.',
558 + 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
559 + '%s uses %s but also contains the following legacy lifecycles:%s%s%s\n\n' +
560 + 'The above lifecycles should be removed. Learn more about this warning here:\n' +
561 + 'https://fb.me/react-async-component-lifecycle-hooks',
562 componentName,
514 - instance.state === null ? 'null' : 'undefined',
563 + newApiName,
564 + foundWillMountName !== null ? `\n ${foundWillMountName}` : '',
565 + foundWillReceivePropsName !== null
566 + ? `\n ${foundWillReceivePropsName}`
567 + : '',
568 + foundWillUpdateName !== null ? `\n ${foundWillUpdateName}` : '',
569 );
570 }
571 }
518 -
519 - // If new component APIs are defined, "unsafe" lifecycles won't be called.
520 - // Warn about these lifecycles if they are present.
521 - // Don't warn about react-lifecycles-compat polyfilled methods though.
522 - if (
523 - typeof ctor.getDerivedStateFromProps === 'function' ||
524 - typeof instance.getSnapshotBeforeUpdate === 'function'
525 - ) {
526 - let foundWillMountName = null;
527 - let foundWillReceivePropsName = null;
528 - let foundWillUpdateName = null;
529 - if (
530 - typeof instance.componentWillMount === 'function' &&
531 - instance.componentWillMount.__suppressDeprecationWarning !== true
532 - ) {
533 - foundWillMountName = 'componentWillMount';
534 - } else if (typeof instance.UNSAFE_componentWillMount === 'function') {
535 - foundWillMountName = 'UNSAFE_componentWillMount';
536 - }
537 - if (
538 - typeof instance.componentWillReceiveProps === 'function' &&
539 - instance.componentWillReceiveProps.__suppressDeprecationWarning !==
540 - true
541 - ) {
542 - foundWillReceivePropsName = 'componentWillReceiveProps';
543 - } else if (
544 - typeof instance.UNSAFE_componentWillReceiveProps === 'function'
545 - ) {
546 - foundWillReceivePropsName = 'UNSAFE_componentWillReceiveProps';
547 - }
548 - if (
549 - typeof instance.componentWillUpdate === 'function' &&
550 - instance.componentWillUpdate.__suppressDeprecationWarning !== true
551 - ) {
552 - foundWillUpdateName = 'componentWillUpdate';
553 - } else if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
554 - foundWillUpdateName = 'UNSAFE_componentWillUpdate';
555 - }
556 - if (
557 - foundWillMountName !== null ||
558 - foundWillReceivePropsName !== null ||
559 - foundWillUpdateName !== null
560 - ) {
561 - const componentName = getComponentName(workInProgress) || 'Component';
562 - const newApiName =
563 - typeof ctor.getDerivedStateFromProps === 'function'
564 - ? 'getDerivedStateFromProps()'
565 - : 'getSnapshotBeforeUpdate()';
566 - if (!didWarnAboutLegacyLifecyclesAndDerivedState.has(componentName)) {
567 - didWarnAboutLegacyLifecyclesAndDerivedState.add(componentName);
568 - warning(
569 - false,
570 - 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
571 - '%s uses %s but also contains the following legacy lifecycles:%s%s%s\n\n' +
572 - 'The above lifecycles should be removed. Learn more about this warning here:\n' +
573 - 'https://fb.me/react-async-component-lifecycle-hooks',
574 - componentName,
575 - newApiName,
576 - foundWillMountName !== null ? `\n ${foundWillMountName}` : '',
577 - foundWillReceivePropsName !== null
578 - ? `\n ${foundWillReceivePropsName}`
579 - : '',
580 - foundWillUpdateName !== null ? `\n ${foundWillUpdateName}` : '',
581 - );
582 - }
583 - }
584 - }
572 }
573 + }
574
587 - // Cache unmasked context so we can avoid recreating masked context unless necessary.
588 - // ReactFiberContext usually updates this cache but can't for newly-created instances.
589 - if (needsContext) {
590 - cacheContext(workInProgress, unmaskedContext, context);
591 - }
575 + // Cache unmasked context so we can avoid recreating masked context unless necessary.
576 + // ReactFiberContext usually updates this cache but can't for newly-created instances.
577 + if (needsContext) {
578 + cacheContext(workInProgress, unmaskedContext, context);
579 + }
580 +
581 + return instance;
582 +}
583
593 - return instance;
584 +function callComponentWillMount(workInProgress, instance) {
585 + startPhaseTimer(workInProgress, 'componentWillMount');
586 + const oldState = instance.state;
587 +
588 + if (typeof instance.componentWillMount === 'function') {
589 + instance.componentWillMount();
590 + }
591 + if (typeof instance.UNSAFE_componentWillMount === 'function') {
592 + instance.UNSAFE_componentWillMount();
593 }
594
596 - function callComponentWillMount(workInProgress, instance) {
597 - startPhaseTimer(workInProgress, 'componentWillMount');
598 - const oldState = instance.state;
595 + stopPhaseTimer();
596
600 - if (typeof instance.componentWillMount === 'function') {
601 - instance.componentWillMount();
602 - }
603 - if (typeof instance.UNSAFE_componentWillMount === 'function') {
604 - instance.UNSAFE_componentWillMount();
597 + if (oldState !== instance.state) {
598 + if (__DEV__) {
599 + warning(
600 + false,
601 + '%s.componentWillMount(): Assigning directly to this.state is ' +
602 + "deprecated (except inside a component's " +
603 + 'constructor). Use setState instead.',
604 + getComponentName(workInProgress) || 'Component',
605 + );
606 }
607 + classComponentUpdater.enqueueReplaceState(instance, instance.state, null);
608 + }
609 +}
610
607 - stopPhaseTimer();
611 +function callComponentWillReceiveProps(
612 + workInProgress,
613 + instance,
614 + newProps,
615 + newContext,
616 +) {
617 + const oldState = instance.state;
618 + startPhaseTimer(workInProgress, 'componentWillReceiveProps');
619 + if (typeof instance.componentWillReceiveProps === 'function') {
620 + instance.componentWillReceiveProps(newProps, newContext);
621 + }
622 + if (typeof instance.UNSAFE_componentWillReceiveProps === 'function') {
623 + instance.UNSAFE_componentWillReceiveProps(newProps, newContext);
624 + }
625 + stopPhaseTimer();
626
609 - if (oldState !== instance.state) {
610 - if (__DEV__) {
627 + if (instance.state !== oldState) {
628 + if (__DEV__) {
629 + const componentName = getComponentName(workInProgress) || 'Component';
630 + if (!didWarnAboutStateAssignmentForComponent.has(componentName)) {
631 + didWarnAboutStateAssignmentForComponent.add(componentName);
632 warning(
633 false,
613 - '%s.componentWillMount(): Assigning directly to this.state is ' +
614 - "deprecated (except inside a component's " +
634 + '%s.componentWillReceiveProps(): Assigning directly to ' +
635 + "this.state is deprecated (except inside a component's " +
636 'constructor). Use setState instead.',
616 - getComponentName(workInProgress) || 'Component',
637 + componentName,
638 );
639 }
619 - classComponentUpdater.enqueueReplaceState(instance, instance.state, null);
640 }
641 + classComponentUpdater.enqueueReplaceState(instance, instance.state, null);
642 }
643 +}
644
623 - function callComponentWillReceiveProps(
624 - workInProgress,
625 - instance,
626 - newProps,
627 - newContext,
628 - ) {
629 - const oldState = instance.state;
630 - startPhaseTimer(workInProgress, 'componentWillReceiveProps');
631 - if (typeof instance.componentWillReceiveProps === 'function') {
632 - instance.componentWillReceiveProps(newProps, newContext);
633 - }
634 - if (typeof instance.UNSAFE_componentWillReceiveProps === 'function') {
635 - instance.UNSAFE_componentWillReceiveProps(newProps, newContext);
636 - }
637 - stopPhaseTimer();
645 +// Invokes the mount life-cycles on a previously never rendered instance.
646 +function mountClassInstance(
647 + workInProgress: Fiber,
648 + renderExpirationTime: ExpirationTime,
649 +): void {
650 + const ctor = workInProgress.type;
651
639 - if (instance.state !== oldState) {
640 - if (__DEV__) {
641 - const componentName = getComponentName(workInProgress) || 'Component';
642 - if (!didWarnAboutStateAssignmentForComponent.has(componentName)) {
643 - didWarnAboutStateAssignmentForComponent.add(componentName);
644 - warning(
645 - false,
646 - '%s.componentWillReceiveProps(): Assigning directly to ' +
647 - "this.state is deprecated (except inside a component's " +
648 - 'constructor). Use setState instead.',
649 - componentName,
650 - );
651 - }
652 - }
653 - classComponentUpdater.enqueueReplaceState(instance, instance.state, null);
654 - }
652 + if (__DEV__) {
653 + checkClassInstance(workInProgress);
654 }
655
657 - // Invokes the mount life-cycles on a previously never rendered instance.
658 - function mountClassInstance(
659 - workInProgress: Fiber,
660 - renderExpirationTime: ExpirationTime,
661 - ): void {
662 - const ctor = workInProgress.type;
656 + const instance = workInProgress.stateNode;
657 + const props = workInProgress.pendingProps;
658 + const unmaskedContext = getUnmaskedContext(workInProgress);
659
664 - if (__DEV__) {
665 - checkClassInstance(workInProgress);
660 + instance.props = props;
661 + instance.state = workInProgress.memoizedState;
662 + instance.refs = emptyObject;
663 + instance.context = getMaskedContext(workInProgress, unmaskedContext);
664 +
665 + if (__DEV__) {
666 + if (workInProgress.mode & StrictMode) {
667 + ReactStrictModeWarnings.recordUnsafeLifecycleWarnings(
668 + workInProgress,
669 + instance,
670 + );
671 }
672
668 - const instance = workInProgress.stateNode;
669 - const props = workInProgress.pendingProps;
670 - const unmaskedContext = getUnmaskedContext(workInProgress);
673 + if (warnAboutDeprecatedLifecycles) {
674 + ReactStrictModeWarnings.recordDeprecationWarnings(
675 + workInProgress,
676 + instance,
677 + );
678 + }
679 + }
680
672 - instance.props = props;
681 + let updateQueue = workInProgress.updateQueue;
682 + if (updateQueue !== null) {
683 + processUpdateQueue(
684 + workInProgress,
685 + updateQueue,
686 + props,
687 + instance,
688 + renderExpirationTime,
689 + );
690 instance.state = workInProgress.memoizedState;
674 - instance.refs = emptyObject;
675 - instance.context = getMaskedContext(workInProgress, unmaskedContext);
676 -
677 - if (__DEV__) {
678 - if (workInProgress.mode & StrictMode) {
679 - ReactStrictModeWarnings.recordUnsafeLifecycleWarnings(
680 - workInProgress,
681 - instance,
682 - );
683 - }
691 + }
692
685 - if (warnAboutDeprecatedLifecycles) {
686 - ReactStrictModeWarnings.recordDeprecationWarnings(
687 - workInProgress,
688 - instance,
689 - );
690 - }
691 - }
693 + const getDerivedStateFromProps = workInProgress.type.getDerivedStateFromProps;
694 + if (typeof getDerivedStateFromProps === 'function') {
695 + applyDerivedStateFromProps(workInProgress, getDerivedStateFromProps, props);
696 + instance.state = workInProgress.memoizedState;
697 + }
698
693 - let updateQueue = workInProgress.updateQueue;
699 + // In order to support react-lifecycles-compat polyfilled components,
700 + // Unsafe lifecycles should not be invoked for components using the new APIs.
701 + if (
702 + typeof ctor.getDerivedStateFromProps !== 'function' &&
703 + typeof instance.getSnapshotBeforeUpdate !== 'function' &&
704 + (typeof instance.UNSAFE_componentWillMount === 'function' ||
705 + typeof instance.componentWillMount === 'function')
706 + ) {
707 + callComponentWillMount(workInProgress, instance);
708 + // If we had additional state updates during this life-cycle, let's
709 + // process them now.
710 + updateQueue = workInProgress.updateQueue;
711 if (updateQueue !== null) {
712 processUpdateQueue(
713 workInProgress,
@@ -701,350 +718,315 @@ export default function(
718 );
719 instance.state = workInProgress.memoizedState;
720 }
721 + }
722
705 - const getDerivedStateFromProps =
706 - workInProgress.type.getDerivedStateFromProps;
707 - if (typeof getDerivedStateFromProps === 'function') {
708 - applyDerivedStateFromProps(
723 + if (typeof instance.componentDidMount === 'function') {
724 + workInProgress.effectTag |= Update;
725 + }
726 +}
727 +
728 +function resumeMountClassInstance(
729 + workInProgress: Fiber,
730 + renderExpirationTime: ExpirationTime,
731 +): boolean {
732 + const ctor = workInProgress.type;
733 + const instance = workInProgress.stateNode;
734 +
735 + const oldProps = workInProgress.memoizedProps;
736 + const newProps = workInProgress.pendingProps;
737 + instance.props = oldProps;
738 +
739 + const oldContext = instance.context;
740 + const newUnmaskedContext = getUnmaskedContext(workInProgress);
741 + const newContext = getMaskedContext(workInProgress, newUnmaskedContext);
742 +
743 + const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
744 + const hasNewLifecycles =
745 + typeof getDerivedStateFromProps === 'function' ||
746 + typeof instance.getSnapshotBeforeUpdate === 'function';
747 +
748 + // Note: During these life-cycles, instance.props/instance.state are what
749 + // ever the previously attempted to render - not the "current". However,
750 + // during componentDidUpdate we pass the "current" props.
751 +
752 + // In order to support react-lifecycles-compat polyfilled components,
753 + // Unsafe lifecycles should not be invoked for components using the new APIs.
754 + if (
755 + !hasNewLifecycles &&
756 + (typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
757 + typeof instance.componentWillReceiveProps === 'function')
758 + ) {
759 + if (oldProps !== newProps || oldContext !== newContext) {
760 + callComponentWillReceiveProps(
761 workInProgress,
710 - getDerivedStateFromProps,
711 - props,
762 + instance,
763 + newProps,
764 + newContext,
765 );
713 - instance.state = workInProgress.memoizedState;
714 - }
715 -
716 - // In order to support react-lifecycles-compat polyfilled components,
717 - // Unsafe lifecycles should not be invoked for components using the new APIs.
718 - if (
719 - typeof ctor.getDerivedStateFromProps !== 'function' &&
720 - typeof instance.getSnapshotBeforeUpdate !== 'function' &&
721 - (typeof instance.UNSAFE_componentWillMount === 'function' ||
722 - typeof instance.componentWillMount === 'function')
723 - ) {
724 - callComponentWillMount(workInProgress, instance);
725 - // If we had additional state updates during this life-cycle, let's
726 - // process them now.
727 - updateQueue = workInProgress.updateQueue;
728 - if (updateQueue !== null) {
729 - processUpdateQueue(
730 - workInProgress,
731 - updateQueue,
732 - props,
733 - instance,
734 - renderExpirationTime,
735 - );
736 - instance.state = workInProgress.memoizedState;
737 - }
766 }
767 + }
768
769 + resetHasForceUpdateBeforeProcessing();
770 +
771 + const oldState = workInProgress.memoizedState;
772 + let newState = (instance.state = oldState);
773 + let updateQueue = workInProgress.updateQueue;
774 + if (updateQueue !== null) {
775 + processUpdateQueue(
776 + workInProgress,
777 + updateQueue,
778 + newProps,
779 + instance,
780 + renderExpirationTime,
781 + );
782 + newState = workInProgress.memoizedState;
783 + }
784 + if (
785 + oldProps === newProps &&
786 + oldState === newState &&
787 + !hasContextChanged() &&
788 + !checkHasForceUpdateAfterProcessing()
789 + ) {
790 + // If an update was already in progress, we should schedule an Update
791 + // effect even though we're bailing out, so that cWU/cDU are called.
792 if (typeof instance.componentDidMount === 'function') {
793 workInProgress.effectTag |= Update;
794 }
795 + return false;
796 }
797
745 - function resumeMountClassInstance(
746 - workInProgress: Fiber,
747 - renderExpirationTime: ExpirationTime,
748 - ): boolean {
749 - const ctor = workInProgress.type;
750 - const instance = workInProgress.stateNode;
751 -
752 - const oldProps = workInProgress.memoizedProps;
753 - const newProps = workInProgress.pendingProps;
754 - instance.props = oldProps;
755 -
756 - const oldContext = instance.context;
757 - const newUnmaskedContext = getUnmaskedContext(workInProgress);
758 - const newContext = getMaskedContext(workInProgress, newUnmaskedContext);
759 -
760 - const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
761 - const hasNewLifecycles =
762 - typeof getDerivedStateFromProps === 'function' ||
763 - typeof instance.getSnapshotBeforeUpdate === 'function';
764 -
765 - // Note: During these life-cycles, instance.props/instance.state are what
766 - // ever the previously attempted to render - not the "current". However,
767 - // during componentDidUpdate we pass the "current" props.
798 + if (typeof getDerivedStateFromProps === 'function') {
799 + applyDerivedStateFromProps(
800 + workInProgress,
801 + getDerivedStateFromProps,
802 + newProps,
803 + );
804 + newState = workInProgress.memoizedState;
805 + }
806
807 + const shouldUpdate =
808 + checkHasForceUpdateAfterProcessing() ||
809 + checkShouldComponentUpdate(
810 + workInProgress,
811 + oldProps,
812 + newProps,
813 + oldState,
814 + newState,
815 + newContext,
816 + );
817 +
818 + if (shouldUpdate) {
819 // In order to support react-lifecycles-compat polyfilled components,
820 // Unsafe lifecycles should not be invoked for components using the new APIs.
821 if (
822 !hasNewLifecycles &&
773 - (typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
774 - typeof instance.componentWillReceiveProps === 'function')
823 + (typeof instance.UNSAFE_componentWillMount === 'function' ||
824 + typeof instance.componentWillMount === 'function')
825 ) {
776 - if (oldProps !== newProps || oldContext !== newContext) {
777 - callComponentWillReceiveProps(
778 - workInProgress,
779 - instance,
780 - newProps,
781 - newContext,
782 - );
826 + startPhaseTimer(workInProgress, 'componentWillMount');
827 + if (typeof instance.componentWillMount === 'function') {
828 + instance.componentWillMount();
829 + }
830 + if (typeof instance.UNSAFE_componentWillMount === 'function') {
831 + instance.UNSAFE_componentWillMount();
832 }
833 + stopPhaseTimer();
834 }
785 -
786 - resetHasForceUpdateBeforeProcessing();
787 -
788 - const oldState = workInProgress.memoizedState;
789 - let newState = (instance.state = oldState);
790 - let updateQueue = workInProgress.updateQueue;
791 - if (updateQueue !== null) {
792 - processUpdateQueue(
793 - workInProgress,
794 - updateQueue,
795 - newProps,
796 - instance,
797 - renderExpirationTime,
798 - );
799 - newState = workInProgress.memoizedState;
835 + if (typeof instance.componentDidMount === 'function') {
836 + workInProgress.effectTag |= Update;
837 }
801 - if (
802 - oldProps === newProps &&
803 - oldState === newState &&
804 - !hasContextChanged() &&
805 - !checkHasForceUpdateAfterProcessing()
806 - ) {
807 - // If an update was already in progress, we should schedule an Update
808 - // effect even though we're bailing out, so that cWU/cDU are called.
809 - if (typeof instance.componentDidMount === 'function') {
810 - workInProgress.effectTag |= Update;
811 - }
812 - return false;
838 + } else {
839 + // If an update was already in progress, we should schedule an Update
840 + // effect even though we're bailing out, so that cWU/cDU are called.
841 + if (typeof instance.componentDidMount === 'function') {
842 + workInProgress.effectTag |= Update;
843 }
844
815 - if (typeof getDerivedStateFromProps === 'function') {
816 - applyDerivedStateFromProps(
817 - workInProgress,
818 - getDerivedStateFromProps,
819 - newProps,
820 - );
821 - newState = workInProgress.memoizedState;
822 - }
845 + // If shouldComponentUpdate returned false, we should still update the
846 + // memoized state to indicate that this work can be reused.
847 + workInProgress.memoizedProps = newProps;
848 + workInProgress.memoizedState = newState;
849 + }
850 +
851 + // Update the existing instance's state, props, and context pointers even
852 + // if shouldComponentUpdate returns false.
853 + instance.props = newProps;
854 + instance.state = newState;
855 + instance.context = newContext;
856 +
857 + return shouldUpdate;
858 +}
859
824 - const shouldUpdate =
825 - checkHasForceUpdateAfterProcessing() ||
826 - checkShouldComponentUpdate(
860 +// Invokes the update life-cycles and returns false if it shouldn't rerender.
861 +function updateClassInstance(
862 + current: Fiber,
863 + workInProgress: Fiber,
864 + renderExpirationTime: ExpirationTime,
865 +): boolean {
866 + const ctor = workInProgress.type;
867 + const instance = workInProgress.stateNode;
868 +
869 + const oldProps = workInProgress.memoizedProps;
870 + const newProps = workInProgress.pendingProps;
871 + instance.props = oldProps;
872 +
873 + const oldContext = instance.context;
874 + const newUnmaskedContext = getUnmaskedContext(workInProgress);
875 + const newContext = getMaskedContext(workInProgress, newUnmaskedContext);
876 +
877 + const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
878 + const hasNewLifecycles =
879 + typeof getDerivedStateFromProps === 'function' ||
880 + typeof instance.getSnapshotBeforeUpdate === 'function';
881 +
882 + // Note: During these life-cycles, instance.props/instance.state are what
883 + // ever the previously attempted to render - not the "current". However,
884 + // during componentDidUpdate we pass the "current" props.
885 +
886 + // In order to support react-lifecycles-compat polyfilled components,
887 + // Unsafe lifecycles should not be invoked for components using the new APIs.
888 + if (
889 + !hasNewLifecycles &&
890 + (typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
891 + typeof instance.componentWillReceiveProps === 'function')
892 + ) {
893 + if (oldProps !== newProps || oldContext !== newContext) {
894 + callComponentWillReceiveProps(
895 workInProgress,
828 - oldProps,
896 + instance,
897 newProps,
830 - oldState,
831 - newState,
898 newContext,
899 );
900 + }
901 + }
902
835 - if (shouldUpdate) {
836 - // In order to support react-lifecycles-compat polyfilled components,
837 - // Unsafe lifecycles should not be invoked for components using the new APIs.
903 + resetHasForceUpdateBeforeProcessing();
904 +
905 + const oldState = workInProgress.memoizedState;
906 + let newState = (instance.state = oldState);
907 + let updateQueue = workInProgress.updateQueue;
908 + if (updateQueue !== null) {
909 + processUpdateQueue(
910 + workInProgress,
911 + updateQueue,
912 + newProps,
913 + instance,
914 + renderExpirationTime,
915 + );
916 + newState = workInProgress.memoizedState;
917 + }
918 +
919 + if (
920 + oldProps === newProps &&
921 + oldState === newState &&
922 + !hasContextChanged() &&
923 + !checkHasForceUpdateAfterProcessing()
924 + ) {
925 + // If an update was already in progress, we should schedule an Update
926 + // effect even though we're bailing out, so that cWU/cDU are called.
927 + if (typeof instance.componentDidUpdate === 'function') {
928 if (
839 - !hasNewLifecycles &&
840 - (typeof instance.UNSAFE_componentWillMount === 'function' ||
841 - typeof instance.componentWillMount === 'function')
929 + oldProps !== current.memoizedProps ||
930 + oldState !== current.memoizedState
931 ) {
843 - startPhaseTimer(workInProgress, 'componentWillMount');
844 - if (typeof instance.componentWillMount === 'function') {
845 - instance.componentWillMount();
846 - }
847 - if (typeof instance.UNSAFE_componentWillMount === 'function') {
848 - instance.UNSAFE_componentWillMount();
849 - }
850 - stopPhaseTimer();
851 - }
852 - if (typeof instance.componentDidMount === 'function') {
932 workInProgress.effectTag |= Update;
933 }
855 - } else {
856 - // If an update was already in progress, we should schedule an Update
857 - // effect even though we're bailing out, so that cWU/cDU are called.
858 - if (typeof instance.componentDidMount === 'function') {
859 - workInProgress.effectTag |= Update;
860 - }
861 -
862 - // If shouldComponentUpdate returned false, we should still update the
863 - // memoized state to indicate that this work can be reused.
864 - workInProgress.memoizedProps = newProps;
865 - workInProgress.memoizedState = newState;
934 }
867 -
868 - // Update the existing instance's state, props, and context pointers even
869 - // if shouldComponentUpdate returns false.
870 - instance.props = newProps;
871 - instance.state = newState;
872 - instance.context = newContext;
873 -
874 - return shouldUpdate;
875 - }
876 -
877 - // Invokes the update life-cycles and returns false if it shouldn't rerender.
878 - function updateClassInstance(
879 - current: Fiber,
880 - workInProgress: Fiber,
881 - renderExpirationTime: ExpirationTime,
882 - ): boolean {
883 - const ctor = workInProgress.type;
884 - const instance = workInProgress.stateNode;
885 -
886 - const oldProps = workInProgress.memoizedProps;
887 - const newProps = workInProgress.pendingProps;
888 - instance.props = oldProps;
889 -
890 - const oldContext = instance.context;
891 - const newUnmaskedContext = getUnmaskedContext(workInProgress);
892 - const newContext = getMaskedContext(workInProgress, newUnmaskedContext);
893 -
894 - const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
895 - const hasNewLifecycles =
896 - typeof getDerivedStateFromProps === 'function' ||
897 - typeof instance.getSnapshotBeforeUpdate === 'function';
898 -
899 - // Note: During these life-cycles, instance.props/instance.state are what
900 - // ever the previously attempted to render - not the "current". However,
901 - // during componentDidUpdate we pass the "current" props.
902 -
903 - // In order to support react-lifecycles-compat polyfilled components,
904 - // Unsafe lifecycles should not be invoked for components using the new APIs.
905 - if (
906 - !hasNewLifecycles &&
907 - (typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
908 - typeof instance.componentWillReceiveProps === 'function')
909 - ) {
910 - if (oldProps !== newProps || oldContext !== newContext) {
911 - callComponentWillReceiveProps(
912 - workInProgress,
913 - instance,
914 - newProps,
915 - newContext,
916 - );
935 + if (typeof instance.getSnapshotBeforeUpdate === 'function') {
936 + if (
937 + oldProps !== current.memoizedProps ||
938 + oldState !== current.memoizedState
939 + ) {
940 + workInProgress.effectTag |= Snapshot;
941 }
942 }
943 + return false;
944 + }
945
920 - resetHasForceUpdateBeforeProcessing();
921 -
922 - const oldState = workInProgress.memoizedState;
923 - let newState = (instance.state = oldState);
924 - let updateQueue = workInProgress.updateQueue;
925 - if (updateQueue !== null) {
926 - processUpdateQueue(
946 + if (typeof getDerivedStateFromProps === 'function') {
947 + if (fireGetDerivedStateFromPropsOnStateUpdates || oldProps !== newProps) {
948 + applyDerivedStateFromProps(
949 workInProgress,
928 - updateQueue,
950 + getDerivedStateFromProps,
951 newProps,
930 - instance,
931 - renderExpirationTime,
952 );
953 newState = workInProgress.memoizedState;
954 }
955 + }
956
957 + const shouldUpdate =
958 + checkHasForceUpdateAfterProcessing() ||
959 + checkShouldComponentUpdate(
960 + workInProgress,
961 + oldProps,
962 + newProps,
963 + oldState,
964 + newState,
965 + newContext,
966 + );
967 +
968 + if (shouldUpdate) {
969 + // In order to support react-lifecycles-compat polyfilled components,
970 + // Unsafe lifecycles should not be invoked for components using the new APIs.
971 if (
937 - oldProps === newProps &&
938 - oldState === newState &&
939 - !hasContextChanged() &&
940 - !checkHasForceUpdateAfterProcessing()
972 + !hasNewLifecycles &&
973 + (typeof instance.UNSAFE_componentWillUpdate === 'function' ||
974 + typeof instance.componentWillUpdate === 'function')
975 ) {
942 - // If an update was already in progress, we should schedule an Update
943 - // effect even though we're bailing out, so that cWU/cDU are called.
944 - if (typeof instance.componentDidUpdate === 'function') {
945 - if (
946 - oldProps !== current.memoizedProps ||
947 - oldState !== current.memoizedState
948 - ) {
949 - workInProgress.effectTag |= Update;
950 - }
976 + startPhaseTimer(workInProgress, 'componentWillUpdate');
977 + if (typeof instance.componentWillUpdate === 'function') {
978 + instance.componentWillUpdate(newProps, newState, newContext);
979 }
952 - if (typeof instance.getSnapshotBeforeUpdate === 'function') {
953 - if (
954 - oldProps !== current.memoizedProps ||
955 - oldState !== current.memoizedState
956 - ) {
957 - workInProgress.effectTag |= Snapshot;
958 - }
980 + if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
981 + instance.UNSAFE_componentWillUpdate(newProps, newState, newContext);
982 }
960 - return false;
983 + stopPhaseTimer();
984 }
962 -
963 - if (typeof getDerivedStateFromProps === 'function') {
964 - if (fireGetDerivedStateFromPropsOnStateUpdates || oldProps !== newProps) {
965 - applyDerivedStateFromProps(
966 - workInProgress,
967 - getDerivedStateFromProps,
968 - newProps,
969 - );
970 - newState = workInProgress.memoizedState;
971 - }
985 + if (typeof instance.componentDidUpdate === 'function') {
986 + workInProgress.effectTag |= Update;
987 }
973 -
974 - const shouldUpdate =
975 - checkHasForceUpdateAfterProcessing() ||
976 - checkShouldComponentUpdate(
977 - workInProgress,
978 - oldProps,
979 - newProps,
980 - oldState,
981 - newState,
982 - newContext,
983 - );
984 -
985 - if (shouldUpdate) {
986 - // In order to support react-lifecycles-compat polyfilled components,
987 - // Unsafe lifecycles should not be invoked for components using the new APIs.
988 + if (typeof instance.getSnapshotBeforeUpdate === 'function') {
989 + workInProgress.effectTag |= Snapshot;
990 + }
991 + } else {
992 + // If an update was already in progress, we should schedule an Update
993 + // effect even though we're bailing out, so that cWU/cDU are called.
994 + if (typeof instance.componentDidUpdate === 'function') {
995 if (
989 - !hasNewLifecycles &&
990 - (typeof instance.UNSAFE_componentWillUpdate === 'function' ||
991 - typeof instance.componentWillUpdate === 'function')
996 + oldProps !== current.memoizedProps ||
997 + oldState !== current.memoizedState
998 ) {
993 - startPhaseTimer(workInProgress, 'componentWillUpdate');
994 - if (typeof instance.componentWillUpdate === 'function') {
995 - instance.componentWillUpdate(newProps, newState, newContext);
996 - }
997 - if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
998 - instance.UNSAFE_componentWillUpdate(newProps, newState, newContext);
999 - }
1000 - stopPhaseTimer();
1001 - }
1002 - if (typeof instance.componentDidUpdate === 'function') {
999 workInProgress.effectTag |= Update;
1000 }
1005 - if (typeof instance.getSnapshotBeforeUpdate === 'function') {
1001 + }
1002 + if (typeof instance.getSnapshotBeforeUpdate === 'function') {
1003 + if (
1004 + oldProps !== current.memoizedProps ||
1005 + oldState !== current.memoizedState
1006 + ) {
1007 workInProgress.effectTag |= Snapshot;
1008 }
1008 - } else {
1009 - // If an update was already in progress, we should schedule an Update
1010 - // effect even though we're bailing out, so that cWU/cDU are called.
1011 - if (typeof instance.componentDidUpdate === 'function') {
1012 - if (
1013 - oldProps !== current.memoizedProps ||
1014 - oldState !== current.memoizedState
1015 - ) {
1016 - workInProgress.effectTag |= Update;
1017 - }
1018 - }
1019 - if (typeof instance.getSnapshotBeforeUpdate === 'function') {
1020 - if (
1021 - oldProps !== current.memoizedProps ||
1022 - oldState !== current.memoizedState
1023 - ) {
1024 - workInProgress.effectTag |= Snapshot;
1025 - }
1026 - }
1027 -
1028 - // If shouldComponentUpdate returned false, we should still update the
1029 - // memoized props/state to indicate that this work can be reused.
1030 - workInProgress.memoizedProps = newProps;
1031 - workInProgress.memoizedState = newState;
1009 }
1010
1034 - // Update the existing instance's state, props, and context pointers even
1035 - // if shouldComponentUpdate returns false.
1036 - instance.props = newProps;
1037 - instance.state = newState;
1038 - instance.context = newContext;
1039 -
1040 - return shouldUpdate;
1011 + // If shouldComponentUpdate returned false, we should still update the
1012 + // memoized props/state to indicate that this work can be reused.
1013 + workInProgress.memoizedProps = newProps;
1014 + workInProgress.memoizedState = newState;
1015 }
1016
1043 - return {
1044 - adoptClassInstance,
1045 - constructClassInstance,
1046 - mountClassInstance,
1047 - resumeMountClassInstance,
1048 - updateClassInstance,
1049 - };
1017 + // Update the existing instance's state, props, and context pointers even
1018 + // if shouldComponentUpdate returns false.
1019 + instance.props = newProps;
1020 + instance.state = newState;
1021 + instance.context = newContext;
1022 +
1023 + return shouldUpdate;
1024 }
1025 +
1026 +export {
1027 + adoptClassInstance,
1028 + constructClassInstance,
1029 + mountClassInstance,
1030 + resumeMountClassInstance,
1031 + updateClassInstance,
1032 +};
packages/react-reconciler/src/ReactFiberCommitWork.js
+661 -684
@@ -7,18 +7,19 @@
7 * @flow
8 */
9
10 -import type {HostConfig} from 'react-reconciler';
10 +import type {
11 + Instance,
12 + TextInstance,
13 + Container,
14 + ChildSet,
15 + UpdatePayload,
16 +} from './ReactFiberHostConfig';
17 import type {Fiber} from './ReactFiber';
18 import type {FiberRoot} from './ReactFiberRoot';
19 import type {ExpirationTime} from './ReactFiberExpirationTime';
20 import type {CapturedValue, CapturedError} from './ReactCapturedValue';
21
16 -import {
17 - enableMutatingReconciler,
18 - enableNoopReconciler,
19 - enablePersistentReconciler,
20 - enableProfilerTimer,
21 -} from 'shared/ReactFeatureFlags';
22 +import {enableProfilerTimer} from 'shared/ReactFeatureFlags';
23 import {
24 ClassComponent,
25 HostRoot,
@@ -44,6 +45,24 @@ import {startPhaseTimer, stopPhaseTimer} from './ReactDebugFiberPerf';
45 import getComponentName from 'shared/getComponentName';
46 import {getStackAddendumByWorkInProgressFiber} from 'shared/ReactFiberComponentTreeHook';
47 import {logCapturedError} from './ReactFiberErrorLogger';
48 +import {
49 + getPublicInstance,
50 + supportsMutation,
51 + supportsPersistence,
52 + commitMount,
53 + commitUpdate,
54 + resetTextContent,
55 + commitTextUpdate,
56 + appendChild,
57 + appendChildToContainer,
58 + insertBefore,
59 + insertInContainerBefore,
60 + removeChild,
61 + removeChildFromContainer,
62 + replaceContainerChildren,
63 + createContainerChildSet,
64 +} from './ReactFiberHostConfig';
65 +import {captureCommitPhaseError} from './ReactFiberScheduler';
66
67 const {
68 invokeGuardedCallback,
@@ -92,779 +111,737 @@ export function logError(boundary: Fiber, errorInfo: CapturedValue<mixed>) {
111 }
112 }
113
95 -export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
96 - config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
97 - captureError: (failedFiber: Fiber, error: mixed) => void,
98 - scheduleWork: (
99 - fiber: Fiber,
100 - startTime: ExpirationTime,
101 - expirationTime: ExpirationTime,
102 - ) => void,
103 - computeExpirationForFiber: (
104 - startTime: ExpirationTime,
105 - fiber: Fiber,
106 - ) => ExpirationTime,
107 - markLegacyErrorBoundaryAsFailed: (instance: mixed) => void,
108 - recalculateCurrentTime: () => ExpirationTime,
109 -) {
110 - const {getPublicInstance, mutation, persistence} = config;
111 -
112 - const callComponentWillUnmountWithTimer = function(current, instance) {
113 - startPhaseTimer(current, 'componentWillUnmount');
114 - instance.props = current.memoizedProps;
115 - instance.state = current.memoizedState;
116 - instance.componentWillUnmount();
117 - stopPhaseTimer();
118 - };
119 -
120 - // Capture errors so they don't interrupt unmounting.
121 - function safelyCallComponentWillUnmount(current, instance) {
122 - if (__DEV__) {
123 - invokeGuardedCallback(
124 - null,
125 - callComponentWillUnmountWithTimer,
126 - null,
127 - current,
128 - instance,
129 - );
130 - if (hasCaughtError()) {
131 - const unmountError = clearCaughtError();
132 - captureError(current, unmountError);
133 - }
134 - } else {
135 - try {
136 - callComponentWillUnmountWithTimer(current, instance);
137 - } catch (unmountError) {
138 - captureError(current, unmountError);
139 - }
114 +const callComponentWillUnmountWithTimer = function(current, instance) {
115 + startPhaseTimer(current, 'componentWillUnmount');
116 + instance.props = current.memoizedProps;
117 + instance.state = current.memoizedState;
118 + instance.componentWillUnmount();
119 + stopPhaseTimer();
120 +};
121 +
122 +// Capture errors so they don't interrupt unmounting.
123 +function safelyCallComponentWillUnmount(current, instance) {
124 + if (__DEV__) {
125 + invokeGuardedCallback(
126 + null,
127 + callComponentWillUnmountWithTimer,
128 + null,
129 + current,
130 + instance,
131 + );
132 + if (hasCaughtError()) {
133 + const unmountError = clearCaughtError();
134 + captureCommitPhaseError(current, unmountError);
135 + }
136 + } else {
137 + try {
138 + callComponentWillUnmountWithTimer(current, instance);
139 + } catch (unmountError) {
140 + captureCommitPhaseError(current, unmountError);
141 }
142 }
143 +}
144
143 - function safelyDetachRef(current: Fiber) {
144 - const ref = current.ref;
145 - if (ref !== null) {
146 - if (typeof ref === 'function') {
147 - if (__DEV__) {
148 - invokeGuardedCallback(null, ref, null, null);
149 - if (hasCaughtError()) {
150 - const refError = clearCaughtError();
151 - captureError(current, refError);
152 - }
153 - } else {
154 - try {
155 - ref(null);
156 - } catch (refError) {
157 - captureError(current, refError);
158 - }
145 +function safelyDetachRef(current: Fiber) {
146 + const ref = current.ref;
147 + if (ref !== null) {
148 + if (typeof ref === 'function') {
149 + if (__DEV__) {
150 + invokeGuardedCallback(null, ref, null, null);
151 + if (hasCaughtError()) {
152 + const refError = clearCaughtError();
153 + captureCommitPhaseError(current, refError);
154 }
155 } else {
161 - ref.current = null;
156 + try {
157 + ref(null);
158 + } catch (refError) {
159 + captureCommitPhaseError(current, refError);
160 + }
161 }
162 + } else {
163 + ref.current = null;
164 }
165 }
166 +}
167
166 - function commitBeforeMutationLifeCycles(
167 - current: Fiber | null,
168 - finishedWork: Fiber,
169 - ): void {
170 - switch (finishedWork.tag) {
171 - case ClassComponent: {
172 - if (finishedWork.effectTag & Snapshot) {
173 - if (current !== null) {
174 - const prevProps = current.memoizedProps;
175 - const prevState = current.memoizedState;
176 - startPhaseTimer(finishedWork, 'getSnapshotBeforeUpdate');
177 - const instance = finishedWork.stateNode;
178 - instance.props = finishedWork.memoizedProps;
179 - instance.state = finishedWork.memoizedState;
180 - const snapshot = instance.getSnapshotBeforeUpdate(
181 - prevProps,
182 - prevState,
183 - );
184 - if (__DEV__) {
185 - const didWarnSet = ((didWarnAboutUndefinedSnapshotBeforeUpdate: any): Set<
186 - mixed,
187 - >);
188 - if (
189 - snapshot === undefined &&
190 - !didWarnSet.has(finishedWork.type)
191 - ) {
192 - didWarnSet.add(finishedWork.type);
193 - warning(
194 - false,
195 - '%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' +
196 - 'must be returned. You have returned undefined.',
197 - getComponentName(finishedWork),
198 - );
199 - }
168 +function commitBeforeMutationLifeCycles(
169 + current: Fiber | null,
170 + finishedWork: Fiber,
171 +): void {
172 + switch (finishedWork.tag) {
173 + case ClassComponent: {
174 + if (finishedWork.effectTag & Snapshot) {
175 + if (current !== null) {
176 + const prevProps = current.memoizedProps;
177 + const prevState = current.memoizedState;
178 + startPhaseTimer(finishedWork, 'getSnapshotBeforeUpdate');
179 + const instance = finishedWork.stateNode;
180 + instance.props = finishedWork.memoizedProps;
181 + instance.state = finishedWork.memoizedState;
182 + const snapshot = instance.getSnapshotBeforeUpdate(
183 + prevProps,
184 + prevState,
185 + );
186 + if (__DEV__) {
187 + const didWarnSet = ((didWarnAboutUndefinedSnapshotBeforeUpdate: any): Set<
188 + mixed,
189 + >);
190 + if (snapshot === undefined && !didWarnSet.has(finishedWork.type)) {
191 + didWarnSet.add(finishedWork.type);
192 + warning(
193 + false,
194 + '%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' +
195 + 'must be returned. You have returned undefined.',
196 + getComponentName(finishedWork),
197 + );
198 }
201 - instance.__reactInternalSnapshotBeforeUpdate = snapshot;
202 - stopPhaseTimer();
199 }
200 + instance.__reactInternalSnapshotBeforeUpdate = snapshot;
201 + stopPhaseTimer();
202 }
205 - return;
206 - }
207 - case HostRoot:
208 - case HostComponent:
209 - case HostText:
210 - case HostPortal:
211 - // Nothing to do for these component types
212 - return;
213 - default: {
214 - invariant(
215 - false,
216 - 'This unit of work tag should not have side-effects. This error is ' +
217 - 'likely caused by a bug in React. Please file an issue.',
218 - );
203 }
204 + return;
205 + }
206 + case HostRoot:
207 + case HostComponent:
208 + case HostText:
209 + case HostPortal:
210 + // Nothing to do for these component types
211 + return;
212 + default: {
213 + invariant(
214 + false,
215 + 'This unit of work tag should not have side-effects. This error is ' +
216 + 'likely caused by a bug in React. Please file an issue.',
217 + );
218 }
219 }
220 +}
221
223 - function commitLifeCycles(
224 - finishedRoot: FiberRoot,
225 - current: Fiber | null,
226 - finishedWork: Fiber,
227 - currentTime: ExpirationTime,
228 - committedExpirationTime: ExpirationTime,
229 - ): void {
230 - switch (finishedWork.tag) {
231 - case ClassComponent: {
232 - const instance = finishedWork.stateNode;
233 - if (finishedWork.effectTag & Update) {
234 - if (current === null) {
235 - startPhaseTimer(finishedWork, 'componentDidMount');
236 - instance.props = finishedWork.memoizedProps;
237 - instance.state = finishedWork.memoizedState;
238 - instance.componentDidMount();
239 - stopPhaseTimer();
240 - } else {
241 - const prevProps = current.memoizedProps;
242 - const prevState = current.memoizedState;
243 - startPhaseTimer(finishedWork, 'componentDidUpdate');
244 - instance.props = finishedWork.memoizedProps;
245 - instance.state = finishedWork.memoizedState;
246 - instance.componentDidUpdate(
247 - prevProps,
248 - prevState,
249 - instance.__reactInternalSnapshotBeforeUpdate,
250 - );
251 - stopPhaseTimer();
252 - }
253 - }
254 - const updateQueue = finishedWork.updateQueue;
255 - if (updateQueue !== null) {
222 +function commitLifeCycles(
223 + finishedRoot: FiberRoot,
224 + current: Fiber | null,
225 + finishedWork: Fiber,
226 + currentTime: ExpirationTime,
227 + committedExpirationTime: ExpirationTime,
228 +): void {
229 + switch (finishedWork.tag) {
230 + case ClassComponent: {
231 + const instance = finishedWork.stateNode;
232 + if (finishedWork.effectTag & Update) {
233 + if (current === null) {
234 + startPhaseTimer(finishedWork, 'componentDidMount');
235 instance.props = finishedWork.memoizedProps;
236 instance.state = finishedWork.memoizedState;
258 - commitUpdateQueue(
259 - finishedWork,
260 - updateQueue,
261 - instance,
262 - committedExpirationTime,
237 + instance.componentDidMount();
238 + stopPhaseTimer();
239 + } else {
240 + const prevProps = current.memoizedProps;
241 + const prevState = current.memoizedState;
242 + startPhaseTimer(finishedWork, 'componentDidUpdate');
243 + instance.props = finishedWork.memoizedProps;
244 + instance.state = finishedWork.memoizedState;
245 + instance.componentDidUpdate(
246 + prevProps,
247 + prevState,
248 + instance.__reactInternalSnapshotBeforeUpdate,
249 );
250 + stopPhaseTimer();
251 }
265 - return;
252 }
267 - case HostRoot: {
268 - const updateQueue = finishedWork.updateQueue;
269 - if (updateQueue !== null) {
270 - let instance = null;
271 - if (finishedWork.child !== null) {
272 - switch (finishedWork.child.tag) {
273 - case HostComponent:
274 - instance = getPublicInstance(finishedWork.child.stateNode);
275 - break;
276 - case ClassComponent:
277 - instance = finishedWork.child.stateNode;
278 - break;
279 - }
280 - }
281 - commitUpdateQueue(
282 - finishedWork,
283 - updateQueue,
284 - instance,
285 - committedExpirationTime,
286 - );
287 - }
288 - return;
253 + const updateQueue = finishedWork.updateQueue;
254 + if (updateQueue !== null) {
255 + instance.props = finishedWork.memoizedProps;
256 + instance.state = finishedWork.memoizedState;
257 + commitUpdateQueue(
258 + finishedWork,
259 + updateQueue,
260 + instance,
261 + committedExpirationTime,
262 + );
263 }
290 - case HostComponent: {
291 - const instance: I = finishedWork.stateNode;
292 -
293 - // Renderers may schedule work to be done after host components are mounted
294 - // (eg DOM renderer may schedule auto-focus for inputs and form controls).
295 - // These effects should only be committed when components are first mounted,
296 - // aka when there is no current/alternate.
297 - if (current === null && finishedWork.effectTag & Update) {
298 - const type = finishedWork.type;
299 - const props = finishedWork.memoizedProps;
300 - commitMount(instance, type, props, finishedWork);
264 + return;
265 + }
266 + case HostRoot: {
267 + const updateQueue = finishedWork.updateQueue;
268 + if (updateQueue !== null) {
269 + let instance = null;
270 + if (finishedWork.child !== null) {
271 + switch (finishedWork.child.tag) {
272 + case HostComponent:
273 + instance = getPublicInstance(finishedWork.child.stateNode);
274 + break;
275 + case ClassComponent:
276 + instance = finishedWork.child.stateNode;
277 + break;
278 + }
279 }
302 -
303 - return;
304 - }
305 - case HostText: {
306 - // We have no life-cycles associated with text.
307 - return;
308 - }
309 - case HostPortal: {
310 - // We have no life-cycles associated with portals.
311 - return;
312 - }
313 - case Profiler: {
314 - // We have no life-cycles associated with Profiler.
315 - return;
316 - }
317 - case TimeoutComponent: {
318 - // We have no life-cycles associated with Timeouts.
319 - return;
320 - }
321 - default: {
322 - invariant(
323 - false,
324 - 'This unit of work tag should not have side-effects. This error is ' +
325 - 'likely caused by a bug in React. Please file an issue.',
280 + commitUpdateQueue(
281 + finishedWork,
282 + updateQueue,
283 + instance,
284 + committedExpirationTime,
285 );
286 }
287 + return;
288 }
329 - }
289 + case HostComponent: {
290 + const instance: Instance = finishedWork.stateNode;
291
331 - function commitAttachRef(finishedWork: Fiber) {
332 - const ref = finishedWork.ref;
333 - if (ref !== null) {
334 - const instance = finishedWork.stateNode;
335 - let instanceToUse;
336 - switch (finishedWork.tag) {
337 - case HostComponent:
338 - instanceToUse = getPublicInstance(instance);
339 - break;
340 - default:
341 - instanceToUse = instance;
292 + // Renderers may schedule work to be done after host components are mounted
293 + // (eg DOM renderer may schedule auto-focus for inputs and form controls).
294 + // These effects should only be committed when components are first mounted,
295 + // aka when there is no current/alternate.
296 + if (current === null && finishedWork.effectTag & Update) {
297 + const type = finishedWork.type;
298 + const props = finishedWork.memoizedProps;
299 + commitMount(instance, type, props, finishedWork);
300 }
343 - if (typeof ref === 'function') {
344 - ref(instanceToUse);
345 - } else {
346 - if (__DEV__) {
347 - if (!ref.hasOwnProperty('current')) {
348 - warning(
349 - false,
350 - 'Unexpected ref object provided for %s. ' +
351 - 'Use either a ref-setter function or React.createRef().%s',
352 - getComponentName(finishedWork),
353 - getStackAddendumByWorkInProgressFiber(finishedWork),
354 - );
355 - }
356 - }
301
358 - ref.current = instanceToUse;
359 - }
302 + return;
303 + }
304 + case HostText: {
305 + // We have no life-cycles associated with text.
306 + return;
307 + }
308 + case HostPortal: {
309 + // We have no life-cycles associated with portals.
310 + return;
311 + }
312 + case Profiler: {
313 + // We have no life-cycles associated with Profiler.
314 + return;
315 + }
316 + case TimeoutComponent: {
317 + // We have no life-cycles associated with Timeouts.
318 + return;
319 + }
320 + default: {
321 + invariant(
322 + false,
323 + 'This unit of work tag should not have side-effects. This error is ' +
324 + 'likely caused by a bug in React. Please file an issue.',
325 + );
326 }
327 }
328 +}
329
363 - function commitDetachRef(current: Fiber) {
364 - const currentRef = current.ref;
365 - if (currentRef !== null) {
366 - if (typeof currentRef === 'function') {
367 - currentRef(null);
368 - } else {
369 - currentRef.current = null;
330 +function commitAttachRef(finishedWork: Fiber) {
331 + const ref = finishedWork.ref;
332 + if (ref !== null) {
333 + const instance = finishedWork.stateNode;
334 + let instanceToUse;
335 + switch (finishedWork.tag) {
336 + case HostComponent:
337 + instanceToUse = getPublicInstance(instance);
338 + break;
339 + default:
340 + instanceToUse = instance;
341 + }
342 + if (typeof ref === 'function') {
343 + ref(instanceToUse);
344 + } else {
345 + if (__DEV__) {
346 + if (!ref.hasOwnProperty('current')) {
347 + warning(
348 + false,
349 + 'Unexpected ref object provided for %s. ' +
350 + 'Use either a ref-setter function or React.createRef().%s',
351 + getComponentName(finishedWork),
352 + getStackAddendumByWorkInProgressFiber(finishedWork),
353 + );
354 + }
355 }
356 +
357 + ref.current = instanceToUse;
358 }
359 }
360 +}
361
374 - // User-originating errors (lifecycles and refs) should not interrupt
375 - // deletion, so don't let them throw. Host-originating errors should
376 - // interrupt deletion, so it's okay
377 - function commitUnmount(current: Fiber): void {
378 - if (typeof onCommitUnmount === 'function') {
379 - onCommitUnmount(current);
362 +function commitDetachRef(current: Fiber) {
363 + const currentRef = current.ref;
364 + if (currentRef !== null) {
365 + if (typeof currentRef === 'function') {
366 + currentRef(null);
367 + } else {
368 + currentRef.current = null;
369 }
370 + }
371 +}
372
382 - switch (current.tag) {
383 - case ClassComponent: {
384 - safelyDetachRef(current);
385 - const instance = current.stateNode;
386 - if (typeof instance.componentWillUnmount === 'function') {
387 - safelyCallComponentWillUnmount(current, instance);
388 - }
389 - return;
390 - }
391 - case HostComponent: {
392 - safelyDetachRef(current);
393 - return;
394 - }
395 - case HostPortal: {
396 - // TODO: this is recursive.
397 - // We are also not using this parent because
398 - // the portal will get pushed immediately.
399 - if (enableMutatingReconciler && mutation) {
400 - unmountHostComponents(current);
401 - } else if (enablePersistentReconciler && persistence) {
402 - emptyPortalContainer(current);
403 - }
404 - return;
373 +// User-originating errors (lifecycles and refs) should not interrupt
374 +// deletion, so don't let them throw. Host-originating errors should
375 +// interrupt deletion, so it's okay
376 +function commitUnmount(current: Fiber): void {
377 + if (typeof onCommitUnmount === 'function') {
378 + onCommitUnmount(current);
379 + }
380 +
381 + switch (current.tag) {
382 + case ClassComponent: {
383 + safelyDetachRef(current);
384 + const instance = current.stateNode;
385 + if (typeof instance.componentWillUnmount === 'function') {
386 + safelyCallComponentWillUnmount(current, instance);
387 }
388 + return;
389 + }
390 + case HostComponent: {
391 + safelyDetachRef(current);
392 + return;
393 + }
394 + case HostPortal: {
395 + // TODO: this is recursive.
396 + // We are also not using this parent because
397 + // the portal will get pushed immediately.
398 + if (supportsMutation) {
399 + unmountHostComponents(current);
400 + } else if (supportsPersistence) {
401 + emptyPortalContainer(current);
402 + }
403 + return;
404 }
405 }
406 +}
407
409 - function commitNestedUnmounts(root: Fiber): void {
410 - // While we're inside a removed host node we don't want to call
411 - // removeChild on the inner nodes because they're removed by the top
412 - // call anyway. We also want to call componentWillUnmount on all
413 - // composites before this host node is removed from the tree. Therefore
414 - // we do an inner loop while we're still inside the host node.
415 - let node: Fiber = root;
416 - while (true) {
417 - commitUnmount(node);
418 - // Visit children because they may contain more composite or host nodes.
419 - // Skip portals because commitUnmount() currently visits them recursively.
420 - if (
421 - node.child !== null &&
422 - // If we use mutation we drill down into portals using commitUnmount above.
423 - // If we don't use mutation we drill down into portals here instead.
424 - (!mutation || node.tag !== HostPortal)
425 - ) {
426 - node.child.return = node;
427 - node = node.child;
428 - continue;
429 - }
430 - if (node === root) {
408 +function commitNestedUnmounts(root: Fiber): void {
409 + // While we're inside a removed host node we don't want to call
410 + // removeChild on the inner nodes because they're removed by the top
411 + // call anyway. We also want to call componentWillUnmount on all
412 + // composites before this host node is removed from the tree. Therefore
413 + // we do an inner loop while we're still inside the host node.
414 + let node: Fiber = root;
415 + while (true) {
416 + commitUnmount(node);
417 + // Visit children because they may contain more composite or host nodes.
418 + // Skip portals because commitUnmount() currently visits them recursively.
419 + if (
420 + node.child !== null &&
421 + // If we use mutation we drill down into portals using commitUnmount above.
422 + // If we don't use mutation we drill down into portals here instead.
423 + (!supportsMutation || node.tag !== HostPortal)
424 + ) {
425 + node.child.return = node;
426 + node = node.child;
427 + continue;
428 + }
429 + if (node === root) {
430 + return;
431 + }
432 + while (node.sibling === null) {
433 + if (node.return === null || node.return === root) {
434 return;
435 }
433 - while (node.sibling === null) {
434 - if (node.return === null || node.return === root) {
435 - return;
436 - }
437 - node = node.return;
438 - }
439 - node.sibling.return = node.return;
440 - node = node.sibling;
436 + node = node.return;
437 }
438 + node.sibling.return = node.return;
439 + node = node.sibling;
440 + }
441 +}
442 +
443 +function detachFiber(current: Fiber) {
444 + // Cut off the return pointers to disconnect it from the tree. Ideally, we
445 + // should clear the child pointer of the parent alternate to let this
446 + // get GC:ed but we don't know which for sure which parent is the current
447 + // one so we'll settle for GC:ing the subtree of this child. This child
448 + // itself will be GC:ed when the parent updates the next time.
449 + current.return = null;
450 + current.child = null;
451 + if (current.alternate) {
452 + current.alternate.child = null;
453 + current.alternate.return = null;
454 + }
455 +}
456 +
457 +function emptyPortalContainer(current: Fiber) {
458 + if (!supportsPersistence) {
459 + return;
460 + }
461 +
462 + const portal: {containerInfo: Container, pendingChildren: ChildSet} =
463 + current.stateNode;
464 + const {containerInfo} = portal;
465 + const emptyChildSet = createContainerChildSet(containerInfo);
466 + replaceContainerChildren(containerInfo, emptyChildSet);
467 +}
468 +
469 +function commitContainer(finishedWork: Fiber) {
470 + if (!supportsPersistence) {
471 + return;
472 }
473
444 - function detachFiber(current: Fiber) {
445 - // Cut off the return pointers to disconnect it from the tree. Ideally, we
446 - // should clear the child pointer of the parent alternate to let this
447 - // get GC:ed but we don't know which for sure which parent is the current
448 - // one so we'll settle for GC:ing the subtree of this child. This child
449 - // itself will be GC:ed when the parent updates the next time.
450 - current.return = null;
451 - current.child = null;
452 - if (current.alternate) {
453 - current.alternate.child = null;
454 - current.alternate.return = null;
474 + switch (finishedWork.tag) {
475 + case ClassComponent: {
476 + return;
477 + }
478 + case HostComponent: {
479 + return;
480 + }
481 + case HostText: {
482 + return;
483 + }
484 + case HostRoot:
485 + case HostPortal: {
486 + const portalOrRoot: {
487 + containerInfo: Container,
488 + pendingChildren: ChildSet,
489 + } =
490 + finishedWork.stateNode;
491 + const {containerInfo, pendingChildren} = portalOrRoot;
492 + replaceContainerChildren(containerInfo, pendingChildren);
493 + return;
494 + }
495 + default: {
496 + invariant(
497 + false,
498 + 'This unit of work tag should not have side-effects. This error is ' +
499 + 'likely caused by a bug in React. Please file an issue.',
500 + );
501 }
502 }
503 +}
504
458 - let emptyPortalContainer;
459 -
460 - if (!mutation) {
461 - let commitContainer;
462 - if (persistence) {
463 - const {replaceContainerChildren, createContainerChildSet} = persistence;
464 - emptyPortalContainer = function(current: Fiber) {
465 - const portal: {containerInfo: C, pendingChildren: CC} =
466 - current.stateNode;
467 - const {containerInfo} = portal;
468 - const emptyChildSet = createContainerChildSet(containerInfo);
469 - replaceContainerChildren(containerInfo, emptyChildSet);
470 - };
471 - commitContainer = function(finishedWork: Fiber) {
472 - switch (finishedWork.tag) {
473 - case ClassComponent: {
474 - return;
475 - }
476 - case HostComponent: {
477 - return;
478 - }
479 - case HostText: {
480 - return;
481 - }
482 - case HostRoot:
483 - case HostPortal: {
484 - const portalOrRoot: {containerInfo: C, pendingChildren: CC} =
485 - finishedWork.stateNode;
486 - const {containerInfo, pendingChildren} = portalOrRoot;
487 - replaceContainerChildren(containerInfo, pendingChildren);
488 - return;
489 - }
490 - default: {
491 - invariant(
492 - false,
493 - 'This unit of work tag should not have side-effects. This error is ' +
494 - 'likely caused by a bug in React. Please file an issue.',
495 - );
496 - }
497 - }
498 - };
499 - } else {
500 - commitContainer = function(finishedWork: Fiber) {
501 - // Noop
502 - };
503 - }
504 - if (enablePersistentReconciler || enableNoopReconciler) {
505 - return {
506 - commitResetTextContent(finishedWork: Fiber) {},
507 - commitPlacement(finishedWork: Fiber) {},
508 - commitDeletion(current: Fiber) {
509 - // Detach refs and call componentWillUnmount() on the whole subtree.
510 - commitNestedUnmounts(current);
511 - detachFiber(current);
512 - },
513 - commitWork(current: Fiber | null, finishedWork: Fiber) {
514 - commitContainer(finishedWork);
515 - },
516 - commitLifeCycles,
517 - commitBeforeMutationLifeCycles,
518 - commitAttachRef,
519 - commitDetachRef,
520 - };
521 - } else if (persistence) {
522 - invariant(false, 'Persistent reconciler is disabled.');
523 - } else {
524 - invariant(false, 'Noop reconciler is disabled.');
505 +function getHostParentFiber(fiber: Fiber): Fiber {
506 + let parent = fiber.return;
507 + while (parent !== null) {
508 + if (isHostParent(parent)) {
509 + return parent;
510 }
511 + parent = parent.return;
512 }
527 - const {
528 - commitMount,
529 - commitUpdate,
530 - resetTextContent,
531 - commitTextUpdate,
532 - appendChild,
533 - appendChildToContainer,
534 - insertBefore,
535 - insertInContainerBefore,
536 - removeChild,
537 - removeChildFromContainer,
538 - } = mutation;
539 -
540 - function getHostParentFiber(fiber: Fiber): Fiber {
541 - let parent = fiber.return;
542 - while (parent !== null) {
543 - if (isHostParent(parent)) {
544 - return parent;
513 + invariant(
514 + false,
515 + 'Expected to find a host parent. This error is likely caused by a bug ' +
516 + 'in React. Please file an issue.',
517 + );
518 +}
519 +
520 +function isHostParent(fiber: Fiber): boolean {
521 + return (
522 + fiber.tag === HostComponent ||
523 + fiber.tag === HostRoot ||
524 + fiber.tag === HostPortal
525 + );
526 +}
527 +
528 +function getHostSibling(fiber: Fiber): ?Instance {
529 + // We're going to search forward into the tree until we find a sibling host
530 + // node. Unfortunately, if multiple insertions are done in a row we have to
531 + // search past them. This leads to exponential search for the next sibling.
532 + // TODO: Find a more efficient way to do this.
533 + let node: Fiber = fiber;
534 + siblings: while (true) {
535 + // If we didn't find anything, let's try the next sibling.
536 + while (node.sibling === null) {
537 + if (node.return === null || isHostParent(node.return)) {
538 + // If we pop out of the root or hit the parent the fiber we are the
539 + // last sibling.
540 + return null;
541 + }
542 + node = node.return;
543 + }
544 + node.sibling.return = node.return;
545 + node = node.sibling;
546 + while (node.tag !== HostComponent && node.tag !== HostText) {
547 + // If it is not host node and, we might have a host node inside it.
548 + // Try to search down until we find one.
549 + if (node.effectTag & Placement) {
550 + // If we don't have a child, try the siblings instead.
551 + continue siblings;
552 + }
553 + // If we don't have a child, try the siblings instead.
554 + // We also skip portals because they are not part of this host tree.
555 + if (node.child === null || node.tag === HostPortal) {
556 + continue siblings;
557 + } else {
558 + node.child.return = node;
559 + node = node.child;
560 }
546 - parent = parent.return;
561 }
548 - invariant(
549 - false,
550 - 'Expected to find a host parent. This error is likely caused by a bug ' +
551 - 'in React. Please file an issue.',
552 - );
562 + // Check if this host node is stable or about to be placed.
563 + if (!(node.effectTag & Placement)) {
564 + // Found it!
565 + return node.stateNode;
566 + }
567 }
568 +}
569
555 - function isHostParent(fiber: Fiber): boolean {
556 - return (
557 - fiber.tag === HostComponent ||
558 - fiber.tag === HostRoot ||
559 - fiber.tag === HostPortal
560 - );
570 +function commitPlacement(finishedWork: Fiber): void {
571 + if (!supportsMutation) {
572 + return;
573 }
574
563 - function getHostSibling(fiber: Fiber): ?I {
564 - // We're going to search forward into the tree until we find a sibling host
565 - // node. Unfortunately, if multiple insertions are done in a row we have to
566 - // search past them. This leads to exponential search for the next sibling.
567 - // TODO: Find a more efficient way to do this.
568 - let node: Fiber = fiber;
569 - siblings: while (true) {
570 - // If we didn't find anything, let's try the next sibling.
571 - while (node.sibling === null) {
572 - if (node.return === null || isHostParent(node.return)) {
573 - // If we pop out of the root or hit the parent the fiber we are the
574 - // last sibling.
575 - return null;
576 - }
577 - node = node.return;
578 - }
579 - node.sibling.return = node.return;
580 - node = node.sibling;
581 - while (node.tag !== HostComponent && node.tag !== HostText) {
582 - // If it is not host node and, we might have a host node inside it.
583 - // Try to search down until we find one.
584 - if (node.effectTag & Placement) {
585 - // If we don't have a child, try the siblings instead.
586 - continue siblings;
575 + // Recursively insert all host nodes into the parent.
576 + const parentFiber = getHostParentFiber(finishedWork);
577 + let parent;
578 + let isContainer;
579 + switch (parentFiber.tag) {
580 + case HostComponent:
581 + parent = parentFiber.stateNode;
582 + isContainer = false;
583 + break;
584 + case HostRoot:
585 + parent = parentFiber.stateNode.containerInfo;
586 + isContainer = true;
587 + break;
588 + case HostPortal:
589 + parent = parentFiber.stateNode.containerInfo;
590 + isContainer = true;
591 + break;
592 + default:
593 + invariant(
594 + false,
595 + 'Invalid host parent fiber. This error is likely caused by a bug ' +
596 + 'in React. Please file an issue.',
597 + );
598 + }
599 + if (parentFiber.effectTag & ContentReset) {
600 + // Reset the text content of the parent before doing any insertions
601 + resetTextContent(parent);
602 + // Clear ContentReset from the effect tag
603 + parentFiber.effectTag &= ~ContentReset;
604 + }
605 +
606 + const before = getHostSibling(finishedWork);
607 + // We only have the top Fiber that was inserted but we need recurse down its
608 + // children to find all the terminal nodes.
609 + let node: Fiber = finishedWork;
610 + while (true) {
611 + if (node.tag === HostComponent || node.tag === HostText) {
612 + if (before) {
613 + if (isContainer) {
614 + insertInContainerBefore(parent, node.stateNode, before);
615 + } else {
616 + insertBefore(parent, node.stateNode, before);
617 }
588 - // If we don't have a child, try the siblings instead.
589 - // We also skip portals because they are not part of this host tree.
590 - if (node.child === null || node.tag === HostPortal) {
591 - continue siblings;
618 + } else {
619 + if (isContainer) {
620 + appendChildToContainer(parent, node.stateNode);
621 } else {
593 - node.child.return = node;
594 - node = node.child;
622 + appendChild(parent, node.stateNode);
623 }
624 }
597 - // Check if this host node is stable or about to be placed.
598 - if (!(node.effectTag & Placement)) {
599 - // Found it!
600 - return node.stateNode;
625 + } else if (node.tag === HostPortal) {
626 + // If the insertion itself is a portal, then we don't want to traverse
627 + // down its children. Instead, we'll get insertions from each child in
628 + // the portal directly.
629 + } else if (node.child !== null) {
630 + node.child.return = node;
631 + node = node.child;
632 + continue;
633 + }
634 + if (node === finishedWork) {
635 + return;
636 + }
637 + while (node.sibling === null) {
638 + if (node.return === null || node.return === finishedWork) {
639 + return;
640 }
641 + node = node.return;
642 }
643 + node.sibling.return = node.return;
644 + node = node.sibling;
645 }
646 +}
647
605 - function commitPlacement(finishedWork: Fiber): void {
606 - // Recursively insert all host nodes into the parent.
607 - const parentFiber = getHostParentFiber(finishedWork);
608 - let parent;
609 - let isContainer;
610 - switch (parentFiber.tag) {
611 - case HostComponent:
612 - parent = parentFiber.stateNode;
613 - isContainer = false;
614 - break;
615 - case HostRoot:
616 - parent = parentFiber.stateNode.containerInfo;
617 - isContainer = true;
618 - break;
619 - case HostPortal:
620 - parent = parentFiber.stateNode.containerInfo;
621 - isContainer = true;
622 - break;
623 - default:
648 +function unmountHostComponents(current): void {
649 + // We only have the top Fiber that was inserted but we need recurse down its
650 + // children to find all the terminal nodes.
651 + let node: Fiber = current;
652 +
653 + // Each iteration, currentParent is populated with node's host parent if not
654 + // currentParentIsValid.
655 + let currentParentIsValid = false;
656 + let currentParent;
657 + let currentParentIsContainer;
658 +
659 + while (true) {
660 + if (!currentParentIsValid) {
661 + let parent = node.return;
662 + findParent: while (true) {
663 invariant(
625 - false,
626 - 'Invalid host parent fiber. This error is likely caused by a bug ' +
627 - 'in React. Please file an issue.',
664 + parent !== null,
665 + 'Expected to find a host parent. This error is likely caused by ' +
666 + 'a bug in React. Please file an issue.',
667 );
629 - }
630 - if (parentFiber.effectTag & ContentReset) {
631 - // Reset the text content of the parent before doing any insertions
632 - resetTextContent(parent);
633 - // Clear ContentReset from the effect tag
634 - parentFiber.effectTag &= ~ContentReset;
635 - }
636 -
637 - const before = getHostSibling(finishedWork);
638 - // We only have the top Fiber that was inserted but we need recurse down its
639 - // children to find all the terminal nodes.
640 - let node: Fiber = finishedWork;
641 - while (true) {
642 - if (node.tag === HostComponent || node.tag === HostText) {
643 - if (before) {
644 - if (isContainer) {
645 - insertInContainerBefore(parent, node.stateNode, before);
646 - } else {
647 - insertBefore(parent, node.stateNode, before);
648 - }
649 - } else {
650 - if (isContainer) {
651 - appendChildToContainer(parent, node.stateNode);
652 - } else {
653 - appendChild(parent, node.stateNode);
654 - }
668 + switch (parent.tag) {
669 + case HostComponent:
670 + currentParent = parent.stateNode;
671 + currentParentIsContainer = false;
672 + break findParent;
673 + case HostRoot:
674 + currentParent = parent.stateNode.containerInfo;
675 + currentParentIsContainer = true;
676 + break findParent;
677 + case HostPortal:
678 + currentParent = parent.stateNode.containerInfo;
679 + currentParentIsContainer = true;
680 + break findParent;
681 }
656 - } else if (node.tag === HostPortal) {
657 - // If the insertion itself is a portal, then we don't want to traverse
658 - // down its children. Instead, we'll get insertions from each child in
659 - // the portal directly.
660 - } else if (node.child !== null) {
682 + parent = parent.return;
683 + }
684 + currentParentIsValid = true;
685 + }
686 +
687 + if (node.tag === HostComponent || node.tag === HostText) {
688 + commitNestedUnmounts(node);
689 + // After all the children have unmounted, it is now safe to remove the
690 + // node from the tree.
691 + if (currentParentIsContainer) {
692 + removeChildFromContainer((currentParent: any), node.stateNode);
693 + } else {
694 + removeChild((currentParent: any), node.stateNode);
695 + }
696 + // Don't visit children because we already visited them.
697 + } else if (node.tag === HostPortal) {
698 + // When we go into a portal, it becomes the parent to remove from.
699 + // We will reassign it back when we pop the portal on the way up.
700 + currentParent = node.stateNode.containerInfo;
701 + // Visit children because portals might contain host components.
702 + if (node.child !== null) {
703 node.child.return = node;
704 node = node.child;
705 continue;
706 }
665 - if (node === finishedWork) {
666 - return;
667 - }
668 - while (node.sibling === null) {
669 - if (node.return === null || node.return === finishedWork) {
670 - return;
671 - }
672 - node = node.return;
707 + } else {
708 + commitUnmount(node);
709 + // Visit children because we may find more host components below.
710 + if (node.child !== null) {
711 + node.child.return = node;
712 + node = node.child;
713 + continue;
714 }
674 - node.sibling.return = node.return;
675 - node = node.sibling;
715 }
677 - }
678 -
679 - function unmountHostComponents(current): void {
680 - // We only have the top Fiber that was inserted but we need recurse down its
681 - // children to find all the terminal nodes.
682 - let node: Fiber = current;
683 -
684 - // Each iteration, currentParent is populated with node's host parent if not
685 - // currentParentIsValid.
686 - let currentParentIsValid = false;
687 - let currentParent;
688 - let currentParentIsContainer;
689 -
690 - while (true) {
691 - if (!currentParentIsValid) {
692 - let parent = node.return;
693 - findParent: while (true) {
694 - invariant(
695 - parent !== null,
696 - 'Expected to find a host parent. This error is likely caused by ' +
697 - 'a bug in React. Please file an issue.',
698 - );
699 - switch (parent.tag) {
700 - case HostComponent:
701 - currentParent = parent.stateNode;
702 - currentParentIsContainer = false;
703 - break findParent;
704 - case HostRoot:
705 - currentParent = parent.stateNode.containerInfo;
706 - currentParentIsContainer = true;
707 - break findParent;
708 - case HostPortal:
709 - currentParent = parent.stateNode.containerInfo;
710 - currentParentIsContainer = true;
711 - break findParent;
712 - }
713 - parent = parent.return;
714 - }
715 - currentParentIsValid = true;
716 - }
717 -
718 - if (node.tag === HostComponent || node.tag === HostText) {
719 - commitNestedUnmounts(node);
720 - // After all the children have unmounted, it is now safe to remove the
721 - // node from the tree.
722 - if (currentParentIsContainer) {
723 - removeChildFromContainer((currentParent: any), node.stateNode);
724 - } else {
725 - removeChild((currentParent: any), node.stateNode);
726 - }
727 - // Don't visit children because we already visited them.
728 - } else if (node.tag === HostPortal) {
729 - // When we go into a portal, it becomes the parent to remove from.
730 - // We will reassign it back when we pop the portal on the way up.
731 - currentParent = node.stateNode.containerInfo;
732 - // Visit children because portals might contain host components.
733 - if (node.child !== null) {
734 - node.child.return = node;
735 - node = node.child;
736 - continue;
737 - }
738 - } else {
739 - commitUnmount(node);
740 - // Visit children because we may find more host components below.
741 - if (node.child !== null) {
742 - node.child.return = node;
743 - node = node.child;
744 - continue;
745 - }
746 - }
747 - if (node === current) {
716 + if (node === current) {
717 + return;
718 + }
719 + while (node.sibling === null) {
720 + if (node.return === null || node.return === current) {
721 return;
722 }
750 - while (node.sibling === null) {
751 - if (node.return === null || node.return === current) {
752 - return;
753 - }
754 - node = node.return;
755 - if (node.tag === HostPortal) {
756 - // When we go out of the portal, we need to restore the parent.
757 - // Since we don't keep a stack of them, we will search for it.
758 - currentParentIsValid = false;
759 - }
723 + node = node.return;
724 + if (node.tag === HostPortal) {
725 + // When we go out of the portal, we need to restore the parent.
726 + // Since we don't keep a stack of them, we will search for it.
727 + currentParentIsValid = false;
728 }
761 - node.sibling.return = node.return;
762 - node = node.sibling;
729 }
730 + node.sibling.return = node.return;
731 + node = node.sibling;
732 }
733 +}
734
766 - function commitDeletion(current: Fiber): void {
735 +function commitDeletion(current: Fiber): void {
736 + if (supportsMutation) {
737 // Recursively delete all host nodes from the parent.
738 // Detach refs and call componentWillUnmount() on the whole subtree.
739 unmountHostComponents(current);
770 - detachFiber(current);
740 + } else {
741 + // Detach refs and call componentWillUnmount() on the whole subtree.
742 + commitNestedUnmounts(current);
743 }
744 + detachFiber(current);
745 +}
746
773 - function commitWork(current: Fiber | null, finishedWork: Fiber): void {
774 - switch (finishedWork.tag) {
775 - case ClassComponent: {
776 - return;
777 - }
778 - case HostComponent: {
779 - const instance: I = finishedWork.stateNode;
780 - if (instance != null) {
781 - // Commit the work prepared earlier.
782 - const newProps = finishedWork.memoizedProps;
783 - // For hydration we reuse the update path but we treat the oldProps
784 - // as the newProps. The updatePayload will contain the real change in
785 - // this case.
786 - const oldProps = current !== null ? current.memoizedProps : newProps;
787 - const type = finishedWork.type;
788 - // TODO: Type the updateQueue to be specific to host components.
789 - const updatePayload: null | PL = (finishedWork.updateQueue: any);
790 - finishedWork.updateQueue = null;
791 - if (updatePayload !== null) {
792 - commitUpdate(
793 - instance,
794 - updatePayload,
795 - type,
796 - oldProps,
797 - newProps,
798 - finishedWork,
799 - );
800 - }
801 - }
802 - return;
803 - }
804 - case HostText: {
805 - invariant(
806 - finishedWork.stateNode !== null,
807 - 'This should have a text node initialized. This error is likely ' +
808 - 'caused by a bug in React. Please file an issue.',
809 - );
810 - const textInstance: TI = finishedWork.stateNode;
811 - const newText: string = finishedWork.memoizedProps;
747 +function commitWork(current: Fiber | null, finishedWork: Fiber): void {
748 + if (!supportsMutation) {
749 + commitContainer(finishedWork);
750 + return;
751 + }
752 +
753 + switch (finishedWork.tag) {
754 + case ClassComponent: {
755 + return;
756 + }
757 + case HostComponent: {
758 + const instance: Instance = finishedWork.stateNode;
759 + if (instance != null) {
760 + // Commit the work prepared earlier.
761 + const newProps = finishedWork.memoizedProps;
762 // For hydration we reuse the update path but we treat the oldProps
763 // as the newProps. The updatePayload will contain the real change in
764 // this case.
815 - const oldText: string =
816 - current !== null ? current.memoizedProps : newText;
817 - commitTextUpdate(textInstance, oldText, newText);
818 - return;
819 - }
820 - case HostRoot: {
821 - return;
822 - }
823 - case Profiler: {
824 - if (enableProfilerTimer) {
825 - const onRender = finishedWork.memoizedProps.onRender;
826 - onRender(
827 - finishedWork.memoizedProps.id,
828 - current === null ? 'mount' : 'update',
829 - finishedWork.stateNode.duration,
830 - finishedWork.treeBaseTime,
765 + const oldProps = current !== null ? current.memoizedProps : newProps;
766 + const type = finishedWork.type;
767 + // TODO: Type the updateQueue to be specific to host components.
768 + const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any);
769 + finishedWork.updateQueue = null;
770 + if (updatePayload !== null) {
771 + commitUpdate(
772 + instance,
773 + updatePayload,
774 + type,
775 + oldProps,
776 + newProps,
777 + finishedWork,
778 );
832 -
833 - // Reset actualTime after successful commit.
834 - // By default, we append to this time to account for errors and pauses.
835 - finishedWork.stateNode.duration = 0;
779 }
837 - return;
838 - }
839 - case TimeoutComponent: {
840 - return;
780 }
842 - default: {
843 - invariant(
844 - false,
845 - 'This unit of work tag should not have side-effects. This error is ' +
846 - 'likely caused by a bug in React. Please file an issue.',
781 + return;
782 + }
783 + case HostText: {
784 + invariant(
785 + finishedWork.stateNode !== null,
786 + 'This should have a text node initialized. This error is likely ' +
787 + 'caused by a bug in React. Please file an issue.',
788 + );
789 + const textInstance: TextInstance = finishedWork.stateNode;
790 + const newText: string = finishedWork.memoizedProps;
791 + // For hydration we reuse the update path but we treat the oldProps
792 + // as the newProps. The updatePayload will contain the real change in
793 + // this case.
794 + const oldText: string =
795 + current !== null ? current.memoizedProps : newText;
796 + commitTextUpdate(textInstance, oldText, newText);
797 + return;
798 + }
799 + case HostRoot: {
800 + return;
801 + }
802 + case Profiler: {
803 + if (enableProfilerTimer) {
804 + const onRender = finishedWork.memoizedProps.onRender;
805 + onRender(
806 + finishedWork.memoizedProps.id,
807 + current === null ? 'mount' : 'update',
808 + finishedWork.stateNode.duration,
809 + finishedWork.treeBaseTime,
810 );
811 +
812 + // Reset actualTime after successful commit.
813 + // By default, we append to this time to account for errors and pauses.
814 + finishedWork.stateNode.duration = 0;
815 }
816 + return;
817 + }
818 + case TimeoutComponent: {
819 + return;
820 + }
821 + default: {
822 + invariant(
823 + false,
824 + 'This unit of work tag should not have side-effects. This error is ' +
825 + 'likely caused by a bug in React. Please file an issue.',
826 + );
827 }
828 }
829 +}
830
852 - function commitResetTextContent(current: Fiber) {
853 - resetTextContent(current.stateNode);
854 - }
855 -
856 - if (enableMutatingReconciler) {
857 - return {
858 - commitBeforeMutationLifeCycles,
859 - commitResetTextContent,
860 - commitPlacement,
861 - commitDeletion,
862 - commitWork,
863 - commitLifeCycles,
864 - commitAttachRef,
865 - commitDetachRef,
866 - };
867 - } else {
868 - invariant(false, 'Mutating reconciler is disabled.');
831 +function commitResetTextContent(current: Fiber) {
832 + if (!supportsMutation) {
833 + return;
834 }
835 + resetTextContent(current.stateNode);
836 }
837 +
838 +export {
839 + commitBeforeMutationLifeCycles,
840 + commitResetTextContent,
841 + commitPlacement,
842 + commitDeletion,
843 + commitWork,
844 + commitLifeCycles,
845 + commitAttachRef,
846 + commitDetachRef,
847 +};
packages/react-reconciler/src/ReactFiberCompleteWork.js
+426 -454
@@ -7,22 +7,20 @@
7 * @flow
8 */
9
10 -import type {HostConfig} from 'react-reconciler';
10 import type {Fiber} from './ReactFiber';
11 import type {ExpirationTime} from './ReactFiberExpirationTime';
13 -import type {HostContext} from './ReactFiberHostContext';
14 -import type {LegacyContext} from './ReactFiberContext';
15 -import type {NewContext} from './ReactFiberNewContext';
16 -import type {HydrationContext} from './ReactFiberHydrationContext';
12 import type {FiberRoot} from './ReactFiberRoot';
18 -import type {ProfilerTimer} from './ReactProfilerTimer';
13 +import type {
14 + Instance,
15 + Type,
16 + Props,
17 + UpdatePayload,
18 + Container,
19 + ChildSet,
20 + HostContext,
21 +} from './ReactFiberHostConfig';
22
20 -import {
21 - enableMutatingReconciler,
22 - enablePersistentReconciler,
23 - enableNoopReconciler,
24 - enableProfilerTimer,
25 -} from 'shared/ReactFeatureFlags';
23 +import {enableProfilerTimer} from 'shared/ReactFeatureFlags';
24 import {
25 IndeterminateComponent,
26 FunctionalComponent,
@@ -42,63 +40,129 @@ import {
40 import {Placement, Ref, Update} from 'shared/ReactTypeOfSideEffect';
41 import invariant from 'fbjs/lib/invariant';
42
45 -export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
46 - config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
47 - hostContext: HostContext<C, CX>,
48 - legacyContext: LegacyContext,
49 - newContext: NewContext,
50 - hydrationContext: HydrationContext<C, CX>,
51 - profilerTimer: ProfilerTimer,
52 -) {
53 - const {
54 - createInstance,
55 - createTextInstance,
56 - appendInitialChild,
57 - finalizeInitialChildren,
58 - prepareUpdate,
59 - mutation,
60 - persistence,
61 - } = config;
62 -
63 - const {
64 - getRootHostContainer,
65 - popHostContext,
66 - getHostContext,
67 - popHostContainer,
68 - } = hostContext;
69 -
70 - const {recordElapsedActualRenderTime} = profilerTimer;
71 -
72 - const {
73 - popContextProvider: popLegacyContextProvider,
74 - popTopLevelContextObject: popTopLevelLegacyContextObject,
75 - } = legacyContext;
43 +import {
44 + createInstance,
45 + createTextInstance,
46 + appendInitialChild,
47 + finalizeInitialChildren,
48 + prepareUpdate,
49 + supportsMutation,
50 + supportsPersistence,
51 + cloneInstance,
52 + createContainerChildSet,
53 + appendChildToContainerChildSet,
54 + finalizeContainerChildren,
55 +} from './ReactFiberHostConfig';
56 +import {
57 + getRootHostContainer,
58 + popHostContext,
59 + getHostContext,
60 + popHostContainer,
61 +} from './ReactFiberHostContext';
62 +import {recordElapsedActualRenderTime} from './ReactProfilerTimer';
63 +import {
64 + popContextProvider as popLegacyContextProvider,
65 + popTopLevelContextObject as popTopLevelLegacyContextObject,
66 +} from './ReactFiberContext';
67 +import {popProvider} from './ReactFiberNewContext';
68 +import {
69 + prepareToHydrateHostInstance,
70 + prepareToHydrateHostTextInstance,
71 + popHydrationState,
72 +} from './ReactFiberHydrationContext';
73
77 - const {popProvider} = newContext;
74 +function markUpdate(workInProgress: Fiber) {
75 + // Tag the fiber with an update effect. This turns a Placement into
76 + // a PlacementAndUpdate.
77 + workInProgress.effectTag |= Update;
78 +}
79
79 - const {
80 - prepareToHydrateHostInstance,
81 - prepareToHydrateHostTextInstance,
82 - popHydrationState,
83 - } = hydrationContext;
80 +function markRef(workInProgress: Fiber) {
81 + workInProgress.effectTag |= Ref;
82 +}
83
85 - function markUpdate(workInProgress: Fiber) {
86 - // Tag the fiber with an update effect. This turns a Placement into
87 - // a PlacementAndUpdate.
88 - workInProgress.effectTag |= Update;
84 +function appendAllChildren(parent: Instance, workInProgress: Fiber) {
85 + // We only have the top Fiber that was created but we need recurse down its
86 + // children to find all the terminal nodes.
87 + let node = workInProgress.child;
88 + while (node !== null) {
89 + if (node.tag === HostComponent || node.tag === HostText) {
90 + appendInitialChild(parent, node.stateNode);
91 + } else if (node.tag === HostPortal) {
92 + // If we have a portal child, then we don't want to traverse
93 + // down its children. Instead, we'll get insertions from each child in
94 + // the portal directly.
95 + } else if (node.child !== null) {
96 + node.child.return = node;
97 + node = node.child;
98 + continue;
99 + }
100 + if (node === workInProgress) {
101 + return;
102 + }
103 + while (node.sibling === null) {
104 + if (node.return === null || node.return === workInProgress) {
105 + return;
106 + }
107 + node = node.return;
108 + }
109 + node.sibling.return = node.return;
110 + node = node.sibling;
111 }
112 +}
113
91 - function markRef(workInProgress: Fiber) {
92 - workInProgress.effectTag |= Ref;
93 - }
114 +let updateHostContainer;
115 +let updateHostComponent;
116 +let updateHostText;
117 +if (supportsMutation) {
118 + // Mutation mode
119 +
120 + updateHostContainer = function(workInProgress: Fiber) {
121 + // Noop
122 + };
123 + updateHostComponent = function(
124 + current: Fiber,
125 + workInProgress: Fiber,
126 + updatePayload: null | UpdatePayload,
127 + type: Type,
128 + oldProps: Props,
129 + newProps: Props,
130 + rootContainerInstance: Container,
131 + currentHostContext: HostContext,
132 + ) {
133 + // TODO: Type this specific to this type of component.
134 + workInProgress.updateQueue = (updatePayload: any);
135 + // If the update payload indicates that there is a change or if there
136 + // is a new ref we mark this as an update. All the work is done in commitWork.
137 + if (updatePayload) {
138 + markUpdate(workInProgress);
139 + }
140 + };
141 + updateHostText = function(
142 + current: Fiber,
143 + workInProgress: Fiber,
144 + oldText: string,
145 + newText: string,
146 + ) {
147 + // If the text differs, mark it as an update. All the work in done in commitWork.
148 + if (oldText !== newText) {
149 + markUpdate(workInProgress);
150 + }
151 + };
152 +} else if (supportsPersistence) {
153 + // Persistent host tree mode
154
95 - function appendAllChildren(parent: I, workInProgress: Fiber) {
155 + // An unfortunate fork of appendAllChildren because we have two different parent types.
156 + const appendAllChildrenToContainer = function(
157 + containerChildSet: ChildSet,
158 + workInProgress: Fiber,
159 + ) {
160 // We only have the top Fiber that was created but we need recurse down its
161 // children to find all the terminal nodes.
162 let node = workInProgress.child;
163 while (node !== null) {
164 if (node.tag === HostComponent || node.tag === HostText) {
101 - appendInitialChild(parent, node.stateNode);
165 + appendChildToContainerChildSet(containerChildSet, node.stateNode);
166 } else if (node.tag === HostPortal) {
167 // If we have a portal child, then we don't want to traverse
168 // down its children. Instead, we'll get insertions from each child in
@@ -120,433 +184,341 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
184 node.sibling.return = node.return;
185 node = node.sibling;
186 }
123 - }
124 -
125 - let updateHostContainer;
126 - let updateHostComponent;
127 - let updateHostText;
128 - if (mutation) {
129 - if (enableMutatingReconciler) {
130 - // Mutation mode
131 - updateHostContainer = function(workInProgress: Fiber) {
132 - // Noop
133 - };
134 - updateHostComponent = function(
135 - current: Fiber,
136 - workInProgress: Fiber,
137 - updatePayload: null | PL,
138 - type: T,
139 - oldProps: P,
140 - newProps: P,
141 - rootContainerInstance: C,
142 - currentHostContext: CX,
143 - ) {
144 - // TODO: Type this specific to this type of component.
145 - workInProgress.updateQueue = (updatePayload: any);
146 - // If the update payload indicates that there is a change or if there
147 - // is a new ref we mark this as an update. All the work is done in commitWork.
148 - if (updatePayload) {
149 - markUpdate(workInProgress);
150 - }
151 - };
152 - updateHostText = function(
153 - current: Fiber,
154 - workInProgress: Fiber,
155 - oldText: string,
156 - newText: string,
157 - ) {
158 - // If the text differs, mark it as an update. All the work in done in commitWork.
159 - if (oldText !== newText) {
160 - markUpdate(workInProgress);
161 - }
162 - };
187 + };
188 + updateHostContainer = function(workInProgress: Fiber) {
189 + const portalOrRoot: {
190 + containerInfo: Container,
191 + pendingChildren: ChildSet,
192 + } =
193 + workInProgress.stateNode;
194 + const childrenUnchanged = workInProgress.firstEffect === null;
195 + if (childrenUnchanged) {
196 + // No changes, just reuse the existing instance.
197 } else {
164 - invariant(false, 'Mutating reconciler is disabled.');
198 + const container = portalOrRoot.containerInfo;
199 + let newChildSet = createContainerChildSet(container);
200 + // If children might have changed, we have to add them all to the set.
201 + appendAllChildrenToContainer(newChildSet, workInProgress);
202 + portalOrRoot.pendingChildren = newChildSet;
203 + // Schedule an update on the container to swap out the container.
204 + markUpdate(workInProgress);
205 + finalizeContainerChildren(container, newChildSet);
206 }
166 - } else if (persistence) {
167 - if (enablePersistentReconciler) {
168 - // Persistent host tree mode
169 - const {
170 - cloneInstance,
171 - createContainerChildSet,
172 - appendChildToContainerChildSet,
173 - finalizeContainerChildren,
174 - } = persistence;
175 -
176 - // An unfortunate fork of appendAllChildren because we have two different parent types.
177 - const appendAllChildrenToContainer = function(
178 - containerChildSet: CC,
179 - workInProgress: Fiber,
207 + };
208 + updateHostComponent = function(
209 + current: Fiber,
210 + workInProgress: Fiber,
211 + updatePayload: null | UpdatePayload,
212 + type: Type,
213 + oldProps: Props,
214 + newProps: Props,
215 + rootContainerInstance: Container,
216 + currentHostContext: HostContext,
217 + ) {
218 + // If there are no effects associated with this node, then none of our children had any updates.
219 + // This guarantees that we can reuse all of them.
220 + const childrenUnchanged = workInProgress.firstEffect === null;
221 + const currentInstance = current.stateNode;
222 + if (childrenUnchanged && updatePayload === null) {
223 + // No changes, just reuse the existing instance.
224 + // Note that this might release a previous clone.
225 + workInProgress.stateNode = currentInstance;
226 + } else {
227 + let recyclableInstance = workInProgress.stateNode;
228 + let newInstance = cloneInstance(
229 + currentInstance,
230 + updatePayload,
231 + type,
232 + oldProps,
233 + newProps,
234 + workInProgress,
235 + childrenUnchanged,
236 + recyclableInstance,
237 + );
238 + if (
239 + finalizeInitialChildren(
240 + newInstance,
241 + type,
242 + newProps,
243 + rootContainerInstance,
244 + currentHostContext,
245 + )
246 ) {
181 - // We only have the top Fiber that was created but we need recurse down its
182 - // children to find all the terminal nodes.
183 - let node = workInProgress.child;
184 - while (node !== null) {
185 - if (node.tag === HostComponent || node.tag === HostText) {
186 - appendChildToContainerChildSet(containerChildSet, node.stateNode);
187 - } else if (node.tag === HostPortal) {
188 - // If we have a portal child, then we don't want to traverse
189 - // down its children. Instead, we'll get insertions from each child in
190 - // the portal directly.
191 - } else if (node.child !== null) {
192 - node.child.return = node;
193 - node = node.child;
194 - continue;
195 - }
196 - if (node === workInProgress) {
197 - return;
198 - }
199 - while (node.sibling === null) {
200 - if (node.return === null || node.return === workInProgress) {
201 - return;
202 - }
203 - node = node.return;
204 - }
205 - node.sibling.return = node.return;
206 - node = node.sibling;
207 - }
208 - };
209 - updateHostContainer = function(workInProgress: Fiber) {
210 - const portalOrRoot: {containerInfo: C, pendingChildren: CC} =
211 - workInProgress.stateNode;
212 - const childrenUnchanged = workInProgress.firstEffect === null;
213 - if (childrenUnchanged) {
214 - // No changes, just reuse the existing instance.
215 - } else {
216 - const container = portalOrRoot.containerInfo;
217 - let newChildSet = createContainerChildSet(container);
218 - // If children might have changed, we have to add them all to the set.
219 - appendAllChildrenToContainer(newChildSet, workInProgress);
220 - portalOrRoot.pendingChildren = newChildSet;
221 - // Schedule an update on the container to swap out the container.
222 - markUpdate(workInProgress);
223 - finalizeContainerChildren(container, newChildSet);
247 + markUpdate(workInProgress);
248 + }
249 + workInProgress.stateNode = newInstance;
250 + if (childrenUnchanged) {
251 + // If there are no other effects in this tree, we need to flag this node as having one.
252 + // Even though we're not going to use it for anything.
253 + // Otherwise parents won't know that there are new children to propagate upwards.
254 + markUpdate(workInProgress);
255 + } else {
256 + // If children might have changed, we have to add them all to the set.
257 + appendAllChildren(newInstance, workInProgress);
258 + }
259 + }
260 + };
261 + updateHostText = function(
262 + current: Fiber,
263 + workInProgress: Fiber,
264 + oldText: string,
265 + newText: string,
266 + ) {
267 + if (oldText !== newText) {
268 + // If the text content differs, we'll create a new text instance for it.
269 + const rootContainerInstance = getRootHostContainer();
270 + const currentHostContext = getHostContext();
271 + workInProgress.stateNode = createTextInstance(
272 + newText,
273 + rootContainerInstance,
274 + currentHostContext,
275 + workInProgress,
276 + );
277 + // We'll have to mark it as having an effect, even though we won't use the effect for anything.
278 + // This lets the parents know that at least one of their children has changed.
279 + markUpdate(workInProgress);
280 + }
281 + };
282 +} else {
283 + // No host operations
284 + updateHostContainer = function(workInProgress: Fiber) {
285 + // Noop
286 + };
287 + updateHostComponent = function(
288 + current: Fiber,
289 + workInProgress: Fiber,
290 + updatePayload: null | UpdatePayload,
291 + type: Type,
292 + oldProps: Props,
293 + newProps: Props,
294 + rootContainerInstance: Container,
295 + currentHostContext: HostContext,
296 + ) {
297 + // Noop
298 + };
299 + updateHostText = function(
300 + current: Fiber,
301 + workInProgress: Fiber,
302 + oldText: string,
303 + newText: string,
304 + ) {
305 + // Noop
306 + };
307 +}
308 +
309 +function completeWork(
310 + current: Fiber | null,
311 + workInProgress: Fiber,
312 + renderExpirationTime: ExpirationTime,
313 +): Fiber | null {
314 + const newProps = workInProgress.pendingProps;
315 + switch (workInProgress.tag) {
316 + case FunctionalComponent:
317 + return null;
318 + case ClassComponent: {
319 + // We are leaving this subtree, so pop context if any.
320 + popLegacyContextProvider(workInProgress);
321 + return null;
322 + }
323 + case HostRoot: {
324 + popHostContainer(workInProgress);
325 + popTopLevelLegacyContextObject(workInProgress);
326 + const fiberRoot = (workInProgress.stateNode: FiberRoot);
327 + if (fiberRoot.pendingContext) {
328 + fiberRoot.context = fiberRoot.pendingContext;
329 + fiberRoot.pendingContext = null;
330 + }
331 + if (current === null || current.child === null) {
332 + // If we hydrated, pop so that we can delete any remaining children
333 + // that weren't hydrated.
334 + popHydrationState(workInProgress);
335 + // This resets the hacky state to fix isMounted before committing.
336 + // TODO: Delete this when we delete isMounted and findDOMNode.
337 + workInProgress.effectTag &= ~Placement;
338 + }
339 + updateHostContainer(workInProgress);
340 + return null;
341 + }
342 + case HostComponent: {
343 + popHostContext(workInProgress);
344 + const rootContainerInstance = getRootHostContainer();
345 + const type = workInProgress.type;
346 + if (current !== null && workInProgress.stateNode != null) {
347 + // If we have an alternate, that means this is an update and we need to
348 + // schedule a side-effect to do the updates.
349 + const oldProps = current.memoizedProps;
350 + // If we get updated because one of our children updated, we don't
351 + // have newProps so we'll have to reuse them.
352 + // TODO: Split the update API as separate for the props vs. children.
353 + // Even better would be if children weren't special cased at all tho.
354 + const instance: Instance = workInProgress.stateNode;
355 + const currentHostContext = getHostContext();
356 + // TODO: Experiencing an error where oldProps is null. Suggests a host
357 + // component is hitting the resume path. Figure out why. Possibly
358 + // related to `hidden`.
359 + const updatePayload = prepareUpdate(
360 + instance,
361 + type,
362 + oldProps,
363 + newProps,
364 + rootContainerInstance,
365 + currentHostContext,
366 + );
367 +
368 + updateHostComponent(
369 + current,
370 + workInProgress,
371 + updatePayload,
372 + type,
373 + oldProps,
374 + newProps,
375 + rootContainerInstance,
376 + currentHostContext,
377 + );
378 +
379 + if (current.ref !== workInProgress.ref) {
380 + markRef(workInProgress);
381 }
225 - };
226 - updateHostComponent = function(
227 - current: Fiber,
228 - workInProgress: Fiber,
229 - updatePayload: null | PL,
230 - type: T,
231 - oldProps: P,
232 - newProps: P,
233 - rootContainerInstance: C,
234 - currentHostContext: CX,
235 - ) {
236 - // If there are no effects associated with this node, then none of our children had any updates.
237 - // This guarantees that we can reuse all of them.
238 - const childrenUnchanged = workInProgress.firstEffect === null;
239 - const currentInstance = current.stateNode;
240 - if (childrenUnchanged && updatePayload === null) {
241 - // No changes, just reuse the existing instance.
242 - // Note that this might release a previous clone.
243 - workInProgress.stateNode = currentInstance;
244 - } else {
245 - let recyclableInstance = workInProgress.stateNode;
246 - let newInstance = cloneInstance(
247 - currentInstance,
248 - updatePayload,
249 - type,
250 - oldProps,
251 - newProps,
252 - workInProgress,
253 - childrenUnchanged,
254 - recyclableInstance,
382 + } else {
383 + if (!newProps) {
384 + invariant(
385 + workInProgress.stateNode !== null,
386 + 'We must have new props for new mounts. This error is likely ' +
387 + 'caused by a bug in React. Please file an issue.',
388 );
389 + // This can happen when we abort work.
390 + return null;
391 + }
392 +
393 + const currentHostContext = getHostContext();
394 + // TODO: Move createInstance to beginWork and keep it on a context
395 + // "stack" as the parent. Then append children as we go in beginWork
396 + // or completeWork depending on we want to add then top->down or
397 + // bottom->up. Top->down is faster in IE11.
398 + let wasHydrated = popHydrationState(workInProgress);
399 + if (wasHydrated) {
400 + // TODO: Move this and createInstance step into the beginPhase
401 + // to consolidate.
402 if (
257 - finalizeInitialChildren(
258 - newInstance,
259 - type,
260 - newProps,
403 + prepareToHydrateHostInstance(
404 + workInProgress,
405 rootContainerInstance,
406 currentHostContext,
407 )
408 ) {
409 + // If changes to the hydrated node needs to be applied at the
410 + // commit-phase we mark this as such.
411 markUpdate(workInProgress);
412 }
267 - workInProgress.stateNode = newInstance;
268 - if (childrenUnchanged) {
269 - // If there are no other effects in this tree, we need to flag this node as having one.
270 - // Even though we're not going to use it for anything.
271 - // Otherwise parents won't know that there are new children to propagate upwards.
272 - markUpdate(workInProgress);
273 - } else {
274 - // If children might have changed, we have to add them all to the set.
275 - appendAllChildren(newInstance, workInProgress);
276 - }
277 - }
278 - };
279 - updateHostText = function(
280 - current: Fiber,
281 - workInProgress: Fiber,
282 - oldText: string,
283 - newText: string,
284 - ) {
285 - if (oldText !== newText) {
286 - // If the text content differs, we'll create a new text instance for it.
287 - const rootContainerInstance = getRootHostContainer();
288 - const currentHostContext = getHostContext();
289 - workInProgress.stateNode = createTextInstance(
290 - newText,
291 - rootContainerInstance,
292 - currentHostContext,
293 - workInProgress,
294 - );
295 - // We'll have to mark it as having an effect, even though we won't use the effect for anything.
296 - // This lets the parents know that at least one of their children has changed.
297 - markUpdate(workInProgress);
298 - }
299 - };
300 - } else {
301 - invariant(false, 'Persistent reconciler is disabled.');
302 - }
303 - } else {
304 - if (enableNoopReconciler) {
305 - // No host operations
306 - updateHostContainer = function(workInProgress: Fiber) {
307 - // Noop
308 - };
309 - updateHostComponent = function(
310 - current: Fiber,
311 - workInProgress: Fiber,
312 - updatePayload: null | PL,
313 - type: T,
314 - oldProps: P,
315 - newProps: P,
316 - rootContainerInstance: C,
317 - currentHostContext: CX,
318 - ) {
319 - // Noop
320 - };
321 - updateHostText = function(
322 - current: Fiber,
323 - workInProgress: Fiber,
324 - oldText: string,
325 - newText: string,
326 - ) {
327 - // Noop
328 - };
329 - } else {
330 - invariant(false, 'Noop reconciler is disabled.');
331 - }
332 - }
333 -
334 - function completeWork(
335 - current: Fiber | null,
336 - workInProgress: Fiber,
337 - renderExpirationTime: ExpirationTime,
338 - ): Fiber | null {
339 - const newProps = workInProgress.pendingProps;
340 - switch (workInProgress.tag) {
341 - case FunctionalComponent:
342 - return null;
343 - case ClassComponent: {
344 - // We are leaving this subtree, so pop context if any.
345 - popLegacyContextProvider(workInProgress);
346 - return null;
347 - }
348 - case HostRoot: {
349 - popHostContainer(workInProgress);
350 - popTopLevelLegacyContextObject(workInProgress);
351 - const fiberRoot = (workInProgress.stateNode: FiberRoot);
352 - if (fiberRoot.pendingContext) {
353 - fiberRoot.context = fiberRoot.pendingContext;
354 - fiberRoot.pendingContext = null;
355 - }
356 - if (current === null || current.child === null) {
357 - // If we hydrated, pop so that we can delete any remaining children
358 - // that weren't hydrated.
359 - popHydrationState(workInProgress);
360 - // This resets the hacky state to fix isMounted before committing.
361 - // TODO: Delete this when we delete isMounted and findDOMNode.
362 - workInProgress.effectTag &= ~Placement;
363 - }
364 - updateHostContainer(workInProgress);
365 - return null;
366 - }
367 - case HostComponent: {
368 - popHostContext(workInProgress);
369 - const rootContainerInstance = getRootHostContainer();
370 - const type = workInProgress.type;
371 - if (current !== null && workInProgress.stateNode != null) {
372 - // If we have an alternate, that means this is an update and we need to
373 - // schedule a side-effect to do the updates.
374 - const oldProps = current.memoizedProps;
375 - // If we get updated because one of our children updated, we don't
376 - // have newProps so we'll have to reuse them.
377 - // TODO: Split the update API as separate for the props vs. children.
378 - // Even better would be if children weren't special cased at all tho.
379 - const instance: I = workInProgress.stateNode;
380 - const currentHostContext = getHostContext();
381 - // TODO: Experiencing an error where oldProps is null. Suggests a host
382 - // component is hitting the resume path. Figure out why. Possibly
383 - // related to `hidden`.
384 - const updatePayload = prepareUpdate(
385 - instance,
413 + } else {
414 + let instance = createInstance(
415 type,
387 - oldProps,
416 newProps,
417 rootContainerInstance,
418 currentHostContext,
391 - );
392 -
393 - updateHostComponent(
394 - current,
419 workInProgress,
396 - updatePayload,
397 - type,
398 - oldProps,
399 - newProps,
400 - rootContainerInstance,
401 - currentHostContext,
420 );
421
404 - if (current.ref !== workInProgress.ref) {
405 - markRef(workInProgress);
406 - }
407 - } else {
408 - if (!newProps) {
409 - invariant(
410 - workInProgress.stateNode !== null,
411 - 'We must have new props for new mounts. This error is likely ' +
412 - 'caused by a bug in React. Please file an issue.',
413 - );
414 - // This can happen when we abort work.
415 - return null;
416 - }
422 + appendAllChildren(instance, workInProgress);
423
418 - const currentHostContext = getHostContext();
419 - // TODO: Move createInstance to beginWork and keep it on a context
420 - // "stack" as the parent. Then append children as we go in beginWork
421 - // or completeWork depending on we want to add then top->down or
422 - // bottom->up. Top->down is faster in IE11.
423 - let wasHydrated = popHydrationState(workInProgress);
424 - if (wasHydrated) {
425 - // TODO: Move this and createInstance step into the beginPhase
426 - // to consolidate.
427 - if (
428 - prepareToHydrateHostInstance(
429 - workInProgress,
430 - rootContainerInstance,
431 - currentHostContext,
432 - )
433 - ) {
434 - // If changes to the hydrated node needs to be applied at the
435 - // commit-phase we mark this as such.
436 - markUpdate(workInProgress);
437 - }
438 - } else {
439 - let instance = createInstance(
424 + // Certain renderers require commit-time effects for initial mount.
425 + // (eg DOM renderer supports auto-focus for certain elements).
426 + // Make sure such renderers get scheduled for later work.
427 + if (
428 + finalizeInitialChildren(
429 + instance,
430 type,
431 newProps,
432 rootContainerInstance,
433 currentHostContext,
444 - workInProgress,
445 - );
446 -
447 - appendAllChildren(instance, workInProgress);
448 -
449 - // Certain renderers require commit-time effects for initial mount.
450 - // (eg DOM renderer supports auto-focus for certain elements).
451 - // Make sure such renderers get scheduled for later work.
452 - if (
453 - finalizeInitialChildren(
454 - instance,
455 - type,
456 - newProps,
457 - rootContainerInstance,
458 - currentHostContext,
459 - )
460 - ) {
461 - markUpdate(workInProgress);
462 - }
463 - workInProgress.stateNode = instance;
434 + )
435 + ) {
436 + markUpdate(workInProgress);
437 }
438 + workInProgress.stateNode = instance;
439 + }
440
466 - if (workInProgress.ref !== null) {
467 - // If there is a ref on a host node we need to schedule a callback
468 - markRef(workInProgress);
469 - }
441 + if (workInProgress.ref !== null) {
442 + // If there is a ref on a host node we need to schedule a callback
443 + markRef(workInProgress);
444 }
471 - return null;
445 }
473 - case HostText: {
474 - let newText = newProps;
475 - if (current && workInProgress.stateNode != null) {
476 - const oldText = current.memoizedProps;
477 - // If we have an alternate, that means this is an update and we need
478 - // to schedule a side-effect to do the updates.
479 - updateHostText(current, workInProgress, oldText, newText);
480 - } else {
481 - if (typeof newText !== 'string') {
482 - invariant(
483 - workInProgress.stateNode !== null,
484 - 'We must have new props for new mounts. This error is likely ' +
485 - 'caused by a bug in React. Please file an issue.',
486 - );
487 - // This can happen when we abort work.
488 - return null;
489 - }
490 - const rootContainerInstance = getRootHostContainer();
491 - const currentHostContext = getHostContext();
492 - let wasHydrated = popHydrationState(workInProgress);
493 - if (wasHydrated) {
494 - if (prepareToHydrateHostTextInstance(workInProgress)) {
495 - markUpdate(workInProgress);
496 - }
497 - } else {
498 - workInProgress.stateNode = createTextInstance(
499 - newText,
500 - rootContainerInstance,
501 - currentHostContext,
502 - workInProgress,
503 - );
446 + return null;
447 + }
448 + case HostText: {
449 + let newText = newProps;
450 + if (current && workInProgress.stateNode != null) {
451 + const oldText = current.memoizedProps;
452 + // If we have an alternate, that means this is an update and we need
453 + // to schedule a side-effect to do the updates.
454 + updateHostText(current, workInProgress, oldText, newText);
455 + } else {
456 + if (typeof newText !== 'string') {
457 + invariant(
458 + workInProgress.stateNode !== null,
459 + 'We must have new props for new mounts. This error is likely ' +
460 + 'caused by a bug in React. Please file an issue.',
461 + );
462 + // This can happen when we abort work.
463 + return null;
464 + }
465 + const rootContainerInstance = getRootHostContainer();
466 + const currentHostContext = getHostContext();
467 + let wasHydrated = popHydrationState(workInProgress);
468 + if (wasHydrated) {
469 + if (prepareToHydrateHostTextInstance(workInProgress)) {
470 + markUpdate(workInProgress);
471 }
472 + } else {
473 + workInProgress.stateNode = createTextInstance(
474 + newText,
475 + rootContainerInstance,
476 + currentHostContext,
477 + workInProgress,
478 + );
479 }
506 - return null;
480 }
508 - case ForwardRef:
509 - return null;
510 - case TimeoutComponent:
511 - return null;
512 - case Fragment:
513 - return null;
514 - case Mode:
515 - return null;
516 - case Profiler:
517 - if (enableProfilerTimer) {
518 - recordElapsedActualRenderTime(workInProgress);
519 - }
520 - return null;
521 - case HostPortal:
522 - popHostContainer(workInProgress);
523 - updateHostContainer(workInProgress);
524 - return null;
525 - case ContextProvider:
526 - // Pop provider fiber
527 - popProvider(workInProgress);
528 - return null;
529 - case ContextConsumer:
530 - return null;
531 - // Error cases
532 - case IndeterminateComponent:
533 - invariant(
534 - false,
535 - 'An indeterminate component should have become determinate before ' +
536 - 'completing. This error is likely caused by a bug in React. Please ' +
537 - 'file an issue.',
538 - );
539 - // eslint-disable-next-line no-fallthrough
540 - default:
541 - invariant(
542 - false,
543 - 'Unknown unit of work tag. This error is likely caused by a bug in ' +
544 - 'React. Please file an issue.',
545 - );
481 + return null;
482 }
483 + case ForwardRef:
484 + return null;
485 + case TimeoutComponent:
486 + return null;
487 + case Fragment:
488 + return null;
489 + case Mode:
490 + return null;
491 + case Profiler:
492 + if (enableProfilerTimer) {
493 + recordElapsedActualRenderTime(workInProgress);
494 + }
495 + return null;
496 + case HostPortal:
497 + popHostContainer(workInProgress);
498 + updateHostContainer(workInProgress);
499 + return null;
500 + case ContextProvider:
501 + // Pop provider fiber
502 + popProvider(workInProgress);
503 + return null;
504 + case ContextConsumer:
505 + return null;
506 + // Error cases
507 + case IndeterminateComponent:
508 + invariant(
509 + false,
510 + 'An indeterminate component should have become determinate before ' +
511 + 'completing. This error is likely caused by a bug in React. Please ' +
512 + 'file an issue.',
513 + );
514 + // eslint-disable-next-line no-fallthrough
515 + default:
516 + invariant(
517 + false,
518 + 'Unknown unit of work tag. This error is likely caused by a bug in ' +
519 + 'React. Please file an issue.',
520 + );
521 }
548 -
549 - return {
550 - completeWork,
551 - };
522 }
523 +
524 +export {completeWork};
packages/react-reconciler/src/ReactFiberContext.js
+243 -270
@@ -8,7 +8,7 @@
8 */
9
10 import type {Fiber} from './ReactFiber';
11 -import type {StackCursor, Stack} from './ReactFiberStack';
11 +import type {StackCursor} from './ReactFiberStack';
12
13 import {isFiberMounted} from 'react-reconciler/reflection';
14 import {ClassComponent, HostRoot} from 'shared/ReactTypeOfWork';
@@ -20,6 +20,7 @@ import checkPropTypes from 'prop-types/checkPropTypes';
20
21 import ReactDebugCurrentFiber from './ReactDebugCurrentFiber';
22 import {startPhaseTimer, stopPhaseTimer} from './ReactDebugFiberPerf';
23 +import {createCursor, push, pop} from './ReactFiberStack';
24
25 let warnedAboutMissingGetChildContext;
26
@@ -27,308 +28,280 @@ if (__DEV__) {
28 warnedAboutMissingGetChildContext = {};
29 }
30
30 -export type LegacyContext = {
31 - getUnmaskedContext(workInProgress: Fiber): Object,
32 - cacheContext(
33 - workInProgress: Fiber,
34 - unmaskedContext: Object,
35 - maskedContext: Object,
36 - ): void,
37 - getMaskedContext(workInProgress: Fiber, unmaskedContext: Object): Object,
38 - hasContextChanged(): boolean,
39 - isContextConsumer(fiber: Fiber): boolean,
40 - isContextProvider(fiber: Fiber): boolean,
41 - popContextProvider(fiber: Fiber): void,
42 - popTopLevelContextObject(fiber: Fiber): void,
43 - pushTopLevelContextObject(
44 - fiber: Fiber,
45 - context: Object,
46 - didChange: boolean,
47 - ): void,
48 - processChildContext(fiber: Fiber, parentContext: Object): Object,
49 - pushContextProvider(workInProgress: Fiber): boolean,
50 - invalidateContextProvider(workInProgress: Fiber, didChange: boolean): void,
51 - findCurrentUnmaskedContext(fiber: Fiber): Object,
52 -};
31 +// A cursor to the current merged context object on the stack.
32 +let contextStackCursor: StackCursor<Object> = createCursor(emptyObject);
33 +// A cursor to a boolean indicating whether the context has changed.
34 +let didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);
35 +// Keep track of the previous context object that was on the stack.
36 +// We use this to get access to the parent context after we have already
37 +// pushed the next context provider, and now need to merge their contexts.
38 +let previousContext: Object = emptyObject;
39 +
40 +function getUnmaskedContext(workInProgress: Fiber): Object {
41 + const hasOwnContext = isContextProvider(workInProgress);
42 + if (hasOwnContext) {
43 + // If the fiber is a context provider itself, when we read its context
44 + // we have already pushed its own child context on the stack. A context
45 + // provider should not "see" its own child context. Therefore we read the
46 + // previous (parent) context instead for a context provider.
47 + return previousContext;
48 + }
49 + return contextStackCursor.current;
50 +}
51
54 -export default function(stack: Stack): LegacyContext {
55 - const {createCursor, push, pop} = stack;
56 -
57 - // A cursor to the current merged context object on the stack.
58 - let contextStackCursor: StackCursor<Object> = createCursor(emptyObject);
59 - // A cursor to a boolean indicating whether the context has changed.
60 - let didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);
61 - // Keep track of the previous context object that was on the stack.
62 - // We use this to get access to the parent context after we have already
63 - // pushed the next context provider, and now need to merge their contexts.
64 - let previousContext: Object = emptyObject;
65 -
66 - function getUnmaskedContext(workInProgress: Fiber): Object {
67 - const hasOwnContext = isContextProvider(workInProgress);
68 - if (hasOwnContext) {
69 - // If the fiber is a context provider itself, when we read its context
70 - // we have already pushed its own child context on the stack. A context
71 - // provider should not "see" its own child context. Therefore we read the
72 - // previous (parent) context instead for a context provider.
73 - return previousContext;
74 - }
75 - return contextStackCursor.current;
52 +function cacheContext(
53 + workInProgress: Fiber,
54 + unmaskedContext: Object,
55 + maskedContext: Object,
56 +): void {
57 + const instance = workInProgress.stateNode;
58 + instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
59 + instance.__reactInternalMemoizedMaskedChildContext = maskedContext;
60 +}
61 +
62 +function getMaskedContext(
63 + workInProgress: Fiber,
64 + unmaskedContext: Object,
65 +): Object {
66 + const type = workInProgress.type;
67 + const contextTypes = type.contextTypes;
68 + if (!contextTypes) {
69 + return emptyObject;
70 }
71
78 - function cacheContext(
79 - workInProgress: Fiber,
80 - unmaskedContext: Object,
81 - maskedContext: Object,
72 + // Avoid recreating masked context unless unmasked context has changed.
73 + // Failing to do this will result in unnecessary calls to componentWillReceiveProps.
74 + // This may trigger infinite loops if componentWillReceiveProps calls setState.
75 + const instance = workInProgress.stateNode;
76 + if (
77 + instance &&
78 + instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext
79 ) {
83 - const instance = workInProgress.stateNode;
84 - instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
85 - instance.__reactInternalMemoizedMaskedChildContext = maskedContext;
80 + return instance.__reactInternalMemoizedMaskedChildContext;
81 }
82
88 - function getMaskedContext(workInProgress: Fiber, unmaskedContext: Object) {
89 - const type = workInProgress.type;
90 - const contextTypes = type.contextTypes;
91 - if (!contextTypes) {
92 - return emptyObject;
93 - }
94 -
95 - // Avoid recreating masked context unless unmasked context has changed.
96 - // Failing to do this will result in unnecessary calls to componentWillReceiveProps.
97 - // This may trigger infinite loops if componentWillReceiveProps calls setState.
98 - const instance = workInProgress.stateNode;
99 - if (
100 - instance &&
101 - instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext
102 - ) {
103 - return instance.__reactInternalMemoizedMaskedChildContext;
104 - }
105 -
106 - const context = {};
107 - for (let key in contextTypes) {
108 - context[key] = unmaskedContext[key];
109 - }
110 -
111 - if (__DEV__) {
112 - const name = getComponentName(workInProgress) || 'Unknown';
113 - checkPropTypes(
114 - contextTypes,
115 - context,
116 - 'context',
117 - name,
118 - ReactDebugCurrentFiber.getCurrentFiberStackAddendum,
119 - );
120 - }
121 -
122 - // Cache unmasked context so we can avoid recreating masked context unless necessary.
123 - // Context is created before the class component is instantiated so check for instance.
124 - if (instance) {
125 - cacheContext(workInProgress, unmaskedContext, context);
126 - }
127 -
128 - return context;
83 + const context = {};
84 + for (let key in contextTypes) {
85 + context[key] = unmaskedContext[key];
86 }
87
131 - function hasContextChanged(): boolean {
132 - return didPerformWorkStackCursor.current;
88 + if (__DEV__) {
89 + const name = getComponentName(workInProgress) || 'Unknown';
90 + checkPropTypes(
91 + contextTypes,
92 + context,
93 + 'context',
94 + name,
95 + ReactDebugCurrentFiber.getCurrentFiberStackAddendum,
96 + );
97 }
98
135 - function isContextConsumer(fiber: Fiber): boolean {
136 - return fiber.tag === ClassComponent && fiber.type.contextTypes != null;
99 + // Cache unmasked context so we can avoid recreating masked context unless necessary.
100 + // Context is created before the class component is instantiated so check for instance.
101 + if (instance) {
102 + cacheContext(workInProgress, unmaskedContext, context);
103 }
104
139 - function isContextProvider(fiber: Fiber): boolean {
140 - return fiber.tag === ClassComponent && fiber.type.childContextTypes != null;
141 - }
105 + return context;
106 +}
107
143 - function popContextProvider(fiber: Fiber): void {
144 - if (!isContextProvider(fiber)) {
145 - return;
146 - }
108 +function hasContextChanged(): boolean {
109 + return didPerformWorkStackCursor.current;
110 +}
111
148 - pop(didPerformWorkStackCursor, fiber);
149 - pop(contextStackCursor, fiber);
150 - }
112 +function isContextConsumer(fiber: Fiber): boolean {
113 + return fiber.tag === ClassComponent && fiber.type.contextTypes != null;
114 +}
115 +
116 +function isContextProvider(fiber: Fiber): boolean {
117 + return fiber.tag === ClassComponent && fiber.type.childContextTypes != null;
118 +}
119
152 - function popTopLevelContextObject(fiber: Fiber) {
153 - pop(didPerformWorkStackCursor, fiber);
154 - pop(contextStackCursor, fiber);
120 +function popContextProvider(fiber: Fiber): void {
121 + if (!isContextProvider(fiber)) {
122 + return;
123 }
124
157 - function pushTopLevelContextObject(
158 - fiber: Fiber,
159 - context: Object,
160 - didChange: boolean,
161 - ): void {
162 - invariant(
163 - contextStackCursor.cursor == null,
164 - 'Unexpected context found on stack. ' +
165 - 'This error is likely caused by a bug in React. Please file an issue.',
166 - );
125 + pop(didPerformWorkStackCursor, fiber);
126 + pop(contextStackCursor, fiber);
127 +}
128
168 - push(contextStackCursor, context, fiber);
169 - push(didPerformWorkStackCursor, didChange, fiber);
170 - }
129 +function popTopLevelContextObject(fiber: Fiber): void {
130 + pop(didPerformWorkStackCursor, fiber);
131 + pop(contextStackCursor, fiber);
132 +}
133
172 - function processChildContext(fiber: Fiber, parentContext: Object): Object {
173 - const instance = fiber.stateNode;
174 - const childContextTypes = fiber.type.childContextTypes;
175 -
176 - // TODO (bvaughn) Replace this behavior with an invariant() in the future.
177 - // It has only been added in Fiber to match the (unintentional) behavior in Stack.
178 - if (typeof instance.getChildContext !== 'function') {
179 - if (__DEV__) {
180 - const componentName = getComponentName(fiber) || 'Unknown';
181 -
182 - if (!warnedAboutMissingGetChildContext[componentName]) {
183 - warnedAboutMissingGetChildContext[componentName] = true;
184 - warning(
185 - false,
186 - '%s.childContextTypes is specified but there is no getChildContext() method ' +
187 - 'on the instance. You can either define getChildContext() on %s or remove ' +
188 - 'childContextTypes from it.',
189 - componentName,
190 - componentName,
191 - );
192 - }
193 - }
194 - return parentContext;
195 - }
134 +function pushTopLevelContextObject(
135 + fiber: Fiber,
136 + context: Object,
137 + didChange: boolean,
138 +): void {
139 + invariant(
140 + contextStackCursor.cursor == null,
141 + 'Unexpected context found on stack. ' +
142 + 'This error is likely caused by a bug in React. Please file an issue.',
143 + );
144 +
145 + push(contextStackCursor, context, fiber);
146 + push(didPerformWorkStackCursor, didChange, fiber);
147 +}
148
197 - let childContext;
198 - if (__DEV__) {
199 - ReactDebugCurrentFiber.setCurrentPhase('getChildContext');
200 - }
201 - startPhaseTimer(fiber, 'getChildContext');
202 - childContext = instance.getChildContext();
203 - stopPhaseTimer();
204 - if (__DEV__) {
205 - ReactDebugCurrentFiber.setCurrentPhase(null);
206 - }
207 - for (let contextKey in childContext) {
208 - invariant(
209 - contextKey in childContextTypes,
210 - '%s.getChildContext(): key "%s" is not defined in childContextTypes.',
211 - getComponentName(fiber) || 'Unknown',
212 - contextKey,
213 - );
214 - }
149 +function processChildContext(fiber: Fiber, parentContext: Object): Object {
150 + const instance = fiber.stateNode;
151 + const childContextTypes = fiber.type.childContextTypes;
152 +
153 + // TODO (bvaughn) Replace this behavior with an invariant() in the future.
154 + // It has only been added in Fiber to match the (unintentional) behavior in Stack.
155 + if (typeof instance.getChildContext !== 'function') {
156 if (__DEV__) {
216 - const name = getComponentName(fiber) || 'Unknown';
217 - checkPropTypes(
218 - childContextTypes,
219 - childContext,
220 - 'child context',
221 - name,
222 - // In practice, there is one case in which we won't get a stack. It's when
223 - // somebody calls unstable_renderSubtreeIntoContainer() and we process
224 - // context from the parent component instance. The stack will be missing
225 - // because it's outside of the reconciliation, and so the pointer has not
226 - // been set. This is rare and doesn't matter. We'll also remove that API.
227 - ReactDebugCurrentFiber.getCurrentFiberStackAddendum,
228 - );
157 + const componentName = getComponentName(fiber) || 'Unknown';
158 +
159 + if (!warnedAboutMissingGetChildContext[componentName]) {
160 + warnedAboutMissingGetChildContext[componentName] = true;
161 + warning(
162 + false,
163 + '%s.childContextTypes is specified but there is no getChildContext() method ' +
164 + 'on the instance. You can either define getChildContext() on %s or remove ' +
165 + 'childContextTypes from it.',
166 + componentName,
167 + componentName,
168 + );
169 + }
170 }
230 -
231 - return {...parentContext, ...childContext};
171 + return parentContext;
172 }
173
234 - function pushContextProvider(workInProgress: Fiber): boolean {
235 - if (!isContextProvider(workInProgress)) {
236 - return false;
237 - }
238 -
239 - const instance = workInProgress.stateNode;
240 - // We push the context as early as possible to ensure stack integrity.
241 - // If the instance does not exist yet, we will push null at first,
242 - // and replace it on the stack later when invalidating the context.
243 - const memoizedMergedChildContext =
244 - (instance && instance.__reactInternalMemoizedMergedChildContext) ||
245 - emptyObject;
246 -
247 - // Remember the parent context so we can merge with it later.
248 - // Inherit the parent's did-perform-work value to avoid inadvertently blocking updates.
249 - previousContext = contextStackCursor.current;
250 - push(contextStackCursor, memoizedMergedChildContext, workInProgress);
251 - push(
252 - didPerformWorkStackCursor,
253 - didPerformWorkStackCursor.current,
254 - workInProgress,
174 + let childContext;
175 + if (__DEV__) {
176 + ReactDebugCurrentFiber.setCurrentPhase('getChildContext');
177 + }
178 + startPhaseTimer(fiber, 'getChildContext');
179 + childContext = instance.getChildContext();
180 + stopPhaseTimer();
181 + if (__DEV__) {
182 + ReactDebugCurrentFiber.setCurrentPhase(null);
183 + }
184 + for (let contextKey in childContext) {
185 + invariant(
186 + contextKey in childContextTypes,
187 + '%s.getChildContext(): key "%s" is not defined in childContextTypes.',
188 + getComponentName(fiber) || 'Unknown',
189 + contextKey,
190 + );
191 + }
192 + if (__DEV__) {
193 + const name = getComponentName(fiber) || 'Unknown';
194 + checkPropTypes(
195 + childContextTypes,
196 + childContext,
197 + 'child context',
198 + name,
199 + // In practice, there is one case in which we won't get a stack. It's when
200 + // somebody calls unstable_renderSubtreeIntoContainer() and we process
201 + // context from the parent component instance. The stack will be missing
202 + // because it's outside of the reconciliation, and so the pointer has not
203 + // been set. This is rare and doesn't matter. We'll also remove that API.
204 + ReactDebugCurrentFiber.getCurrentFiberStackAddendum,
205 );
206 + }
207 +
208 + return {...parentContext, ...childContext};
209 +}
210
257 - return true;
211 +function pushContextProvider(workInProgress: Fiber): boolean {
212 + if (!isContextProvider(workInProgress)) {
213 + return false;
214 }
215
260 - function invalidateContextProvider(
261 - workInProgress: Fiber,
262 - didChange: boolean,
263 - ): void {
264 - const instance = workInProgress.stateNode;
265 - invariant(
266 - instance,
267 - 'Expected to have an instance by this point. ' +
268 - 'This error is likely caused by a bug in React. Please file an issue.',
269 - );
216 + const instance = workInProgress.stateNode;
217 + // We push the context as early as possible to ensure stack integrity.
218 + // If the instance does not exist yet, we will push null at first,
219 + // and replace it on the stack later when invalidating the context.
220 + const memoizedMergedChildContext =
221 + (instance && instance.__reactInternalMemoizedMergedChildContext) ||
222 + emptyObject;
223 +
224 + // Remember the parent context so we can merge with it later.
225 + // Inherit the parent's did-perform-work value to avoid inadvertently blocking updates.
226 + previousContext = contextStackCursor.current;
227 + push(contextStackCursor, memoizedMergedChildContext, workInProgress);
228 + push(
229 + didPerformWorkStackCursor,
230 + didPerformWorkStackCursor.current,
231 + workInProgress,
232 + );
233 +
234 + return true;
235 +}
236
271 - if (didChange) {
272 - // Merge parent and own context.
273 - // Skip this if we're not updating due to sCU.
274 - // This avoids unnecessarily recomputing memoized values.
275 - const mergedContext = processChildContext(
276 - workInProgress,
277 - previousContext,
278 - );
279 - instance.__reactInternalMemoizedMergedChildContext = mergedContext;
280 -
281 - // Replace the old (or empty) context with the new one.
282 - // It is important to unwind the context in the reverse order.
283 - pop(didPerformWorkStackCursor, workInProgress);
284 - pop(contextStackCursor, workInProgress);
285 - // Now push the new context and mark that it has changed.
286 - push(contextStackCursor, mergedContext, workInProgress);
287 - push(didPerformWorkStackCursor, didChange, workInProgress);
288 - } else {
289 - pop(didPerformWorkStackCursor, workInProgress);
290 - push(didPerformWorkStackCursor, didChange, workInProgress);
291 - }
237 +function invalidateContextProvider(
238 + workInProgress: Fiber,
239 + didChange: boolean,
240 +): void {
241 + const instance = workInProgress.stateNode;
242 + invariant(
243 + instance,
244 + 'Expected to have an instance by this point. ' +
245 + 'This error is likely caused by a bug in React. Please file an issue.',
246 + );
247 +
248 + if (didChange) {
249 + // Merge parent and own context.
250 + // Skip this if we're not updating due to sCU.
251 + // This avoids unnecessarily recomputing memoized values.
252 + const mergedContext = processChildContext(workInProgress, previousContext);
253 + instance.__reactInternalMemoizedMergedChildContext = mergedContext;
254 +
255 + // Replace the old (or empty) context with the new one.
256 + // It is important to unwind the context in the reverse order.
257 + pop(didPerformWorkStackCursor, workInProgress);
258 + pop(contextStackCursor, workInProgress);
259 + // Now push the new context and mark that it has changed.
260 + push(contextStackCursor, mergedContext, workInProgress);
261 + push(didPerformWorkStackCursor, didChange, workInProgress);
262 + } else {
263 + pop(didPerformWorkStackCursor, workInProgress);
264 + push(didPerformWorkStackCursor, didChange, workInProgress);
265 }
266 +}
267
294 - function findCurrentUnmaskedContext(fiber: Fiber): Object {
295 - // Currently this is only used with renderSubtreeIntoContainer; not sure if it
296 - // makes sense elsewhere
268 +function findCurrentUnmaskedContext(fiber: Fiber): Object {
269 + // Currently this is only used with renderSubtreeIntoContainer; not sure if it
270 + // makes sense elsewhere
271 + invariant(
272 + isFiberMounted(fiber) && fiber.tag === ClassComponent,
273 + 'Expected subtree parent to be a mounted class component. ' +
274 + 'This error is likely caused by a bug in React. Please file an issue.',
275 + );
276 +
277 + let node: Fiber = fiber;
278 + while (node.tag !== HostRoot) {
279 + if (isContextProvider(node)) {
280 + return node.stateNode.__reactInternalMemoizedMergedChildContext;
281 + }
282 + const parent = node.return;
283 invariant(
298 - isFiberMounted(fiber) && fiber.tag === ClassComponent,
299 - 'Expected subtree parent to be a mounted class component. ' +
284 + parent,
285 + 'Found unexpected detached subtree parent. ' +
286 'This error is likely caused by a bug in React. Please file an issue.',
287 );
302 -
303 - let node: Fiber = fiber;
304 - while (node.tag !== HostRoot) {
305 - if (isContextProvider(node)) {
306 - return node.stateNode.__reactInternalMemoizedMergedChildContext;
307 - }
308 - const parent = node.return;
309 - invariant(
310 - parent,
311 - 'Found unexpected detached subtree parent. ' +
312 - 'This error is likely caused by a bug in React. Please file an issue.',
313 - );
314 - node = parent;
315 - }
316 - return node.stateNode.context;
288 + node = parent;
289 }
318 -
319 - return {
320 - getUnmaskedContext,
321 - cacheContext,
322 - getMaskedContext,
323 - hasContextChanged,
324 - isContextConsumer,
325 - isContextProvider,
326 - popContextProvider,
327 - popTopLevelContextObject,
328 - pushTopLevelContextObject,
329 - processChildContext,
330 - pushContextProvider,
331 - invalidateContextProvider,
332 - findCurrentUnmaskedContext,
333 - };
290 + return node.stateNode.context;
291 }
292 +
293 +export {
294 + getUnmaskedContext,
295 + cacheContext,
296 + getMaskedContext,
297 + hasContextChanged,
298 + isContextConsumer,
299 + isContextProvider,
300 + popContextProvider,
301 + popTopLevelContextObject,
302 + pushTopLevelContextObject,
303 + processChildContext,
304 + pushContextProvider,
305 + invalidateContextProvider,
306 + findCurrentUnmaskedContext,
307 +};
packages/react-reconciler/src/ReactFiberHostConfig.js new
+20
@@ -0,0 +1,20 @@
1 +/**
2 + * Copyright (c) 2015-present, Facebook, Inc.
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 +import invariant from 'fbjs/lib/invariant';
11 +
12 +// We expect that our Rollup, Jest, and Flow configurations
13 +// always shim this module with the corresponding host config
14 +// (either provided by a renderer, or a generic shim for npm).
15 +//
16 +// We should never resolve to this file, but it exists to make
17 +// sure that if we *do* accidentally break the configuration,
18 +// the failure isn't silent.
19 +
20 +invariant(false, 'This module must be shimmed by a specific renderer.');
packages/react-reconciler/src/ReactFiberHostContext.js
+84 -96
@@ -7,119 +7,107 @@
7 * @flow
8 */
9
10 -import type {HostConfig} from 'react-reconciler';
10 import type {Fiber} from './ReactFiber';
12 -import type {StackCursor, Stack} from './ReactFiberStack';
11 +import type {StackCursor} from './ReactFiberStack';
12 +import type {Container, HostContext} from './ReactFiberHostConfig';
13
14 import invariant from 'fbjs/lib/invariant';
15
16 +import {getChildHostContext, getRootHostContext} from './ReactFiberHostConfig';
17 +import {createCursor, push, pop} from './ReactFiberStack';
18 +
19 declare class NoContextT {}
20 const NO_CONTEXT: NoContextT = ({}: any);
21
19 -export type HostContext<C, CX> = {
20 - getHostContext(): CX,
21 - getRootHostContainer(): C,
22 - popHostContainer(fiber: Fiber): void,
23 - popHostContext(fiber: Fiber): void,
24 - pushHostContainer(fiber: Fiber, container: C): void,
25 - pushHostContext(fiber: Fiber): void,
26 -};
27 -
28 -export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
29 - config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
30 - stack: Stack,
31 -): HostContext<C, CX> {
32 - const {getChildHostContext, getRootHostContext} = config;
33 - const {createCursor, push, pop} = stack;
34 -
35 - let contextStackCursor: StackCursor<CX | NoContextT> = createCursor(
36 - NO_CONTEXT,
37 - );
38 - let contextFiberStackCursor: StackCursor<Fiber | NoContextT> = createCursor(
39 - NO_CONTEXT,
40 - );
41 - let rootInstanceStackCursor: StackCursor<C | NoContextT> = createCursor(
42 - NO_CONTEXT,
22 +let contextStackCursor: StackCursor<HostContext | NoContextT> = createCursor(
23 + NO_CONTEXT,
24 +);
25 +let contextFiberStackCursor: StackCursor<Fiber | NoContextT> = createCursor(
26 + NO_CONTEXT,
27 +);
28 +let rootInstanceStackCursor: StackCursor<Container | NoContextT> = createCursor(
29 + NO_CONTEXT,
30 +);
31 +
32 +function requiredContext<Value>(c: Value | NoContextT): Value {
33 + invariant(
34 + c !== NO_CONTEXT,
35 + 'Expected host context to exist. This error is likely caused by a bug ' +
36 + 'in React. Please file an issue.',
37 );
38 + return (c: any);
39 +}
40
45 - function requiredContext<Value>(c: Value | NoContextT): Value {
46 - invariant(
47 - c !== NO_CONTEXT,
48 - 'Expected host context to exist. This error is likely caused by a bug ' +
49 - 'in React. Please file an issue.',
50 - );
51 - return (c: any);
52 - }
53 -
54 - function getRootHostContainer(): C {
55 - const rootInstance = requiredContext(rootInstanceStackCursor.current);
56 - return rootInstance;
57 - }
58 -
59 - function pushHostContainer(fiber: Fiber, nextRootInstance: C) {
60 - // Push current root instance onto the stack;
61 - // This allows us to reset root when portals are popped.
62 - push(rootInstanceStackCursor, nextRootInstance, fiber);
63 - // Track the context and the Fiber that provided it.
64 - // This enables us to pop only Fibers that provide unique contexts.
65 - push(contextFiberStackCursor, fiber, fiber);
66 -
67 - // Finally, we need to push the host context to the stack.
68 - // However, we can't just call getRootHostContext() and push it because
69 - // we'd have a different number of entries on the stack depending on
70 - // whether getRootHostContext() throws somewhere in renderer code or not.
71 - // So we push an empty value first. This lets us safely unwind on errors.
72 - push(contextStackCursor, NO_CONTEXT, fiber);
73 - const nextRootContext = getRootHostContext(nextRootInstance);
74 - // Now that we know this function doesn't throw, replace it.
75 - pop(contextStackCursor, fiber);
76 - push(contextStackCursor, nextRootContext, fiber);
77 - }
41 +function getRootHostContainer(): Container {
42 + const rootInstance = requiredContext(rootInstanceStackCursor.current);
43 + return rootInstance;
44 +}
45
79 - function popHostContainer(fiber: Fiber) {
80 - pop(contextStackCursor, fiber);
81 - pop(contextFiberStackCursor, fiber);
82 - pop(rootInstanceStackCursor, fiber);
83 - }
46 +function pushHostContainer(fiber: Fiber, nextRootInstance: Container) {
47 + // Push current root instance onto the stack;
48 + // This allows us to reset root when portals are popped.
49 + push(rootInstanceStackCursor, nextRootInstance, fiber);
50 + // Track the context and the Fiber that provided it.
51 + // This enables us to pop only Fibers that provide unique contexts.
52 + push(contextFiberStackCursor, fiber, fiber);
53 +
54 + // Finally, we need to push the host context to the stack.
55 + // However, we can't just call getRootHostContext() and push it because
56 + // we'd have a different number of entries on the stack depending on
57 + // whether getRootHostContext() throws somewhere in renderer code or not.
58 + // So we push an empty value first. This lets us safely unwind on errors.
59 + push(contextStackCursor, NO_CONTEXT, fiber);
60 + const nextRootContext = getRootHostContext(nextRootInstance);
61 + // Now that we know this function doesn't throw, replace it.
62 + pop(contextStackCursor, fiber);
63 + push(contextStackCursor, nextRootContext, fiber);
64 +}
65
85 - function getHostContext(): CX {
86 - const context = requiredContext(contextStackCursor.current);
87 - return context;
88 - }
66 +function popHostContainer(fiber: Fiber) {
67 + pop(contextStackCursor, fiber);
68 + pop(contextFiberStackCursor, fiber);
69 + pop(rootInstanceStackCursor, fiber);
70 +}
71
90 - function pushHostContext(fiber: Fiber): void {
91 - const rootInstance: C = requiredContext(rootInstanceStackCursor.current);
92 - const context: CX = requiredContext(contextStackCursor.current);
93 - const nextContext = getChildHostContext(context, fiber.type, rootInstance);
72 +function getHostContext(): HostContext {
73 + const context = requiredContext(contextStackCursor.current);
74 + return context;
75 +}
76
95 - // Don't push this Fiber's context unless it's unique.
96 - if (context === nextContext) {
97 - return;
98 - }
77 +function pushHostContext(fiber: Fiber): void {
78 + const rootInstance: Container = requiredContext(
79 + rootInstanceStackCursor.current,
80 + );
81 + const context: HostContext = requiredContext(contextStackCursor.current);
82 + const nextContext = getChildHostContext(context, fiber.type, rootInstance);
83
100 - // Track the context and the Fiber that provided it.
101 - // This enables us to pop only Fibers that provide unique contexts.
102 - push(contextFiberStackCursor, fiber, fiber);
103 - push(contextStackCursor, nextContext, fiber);
84 + // Don't push this Fiber's context unless it's unique.
85 + if (context === nextContext) {
86 + return;
87 }
88
106 - function popHostContext(fiber: Fiber): void {
107 - // Do not pop unless this Fiber provided the current context.
108 - // pushHostContext() only pushes Fibers that provide unique contexts.
109 - if (contextFiberStackCursor.current !== fiber) {
110 - return;
111 - }
89 + // Track the context and the Fiber that provided it.
90 + // This enables us to pop only Fibers that provide unique contexts.
91 + push(contextFiberStackCursor, fiber, fiber);
92 + push(contextStackCursor, nextContext, fiber);
93 +}
94
113 - pop(contextStackCursor, fiber);
114 - pop(contextFiberStackCursor, fiber);
95 +function popHostContext(fiber: Fiber): void {
96 + // Do not pop unless this Fiber provided the current context.
97 + // pushHostContext() only pushes Fibers that provide unique contexts.
98 + if (contextFiberStackCursor.current !== fiber) {
99 + return;
100 }
101
117 - return {
118 - getHostContext,
119 - getRootHostContainer,
120 - popHostContainer,
121 - popHostContext,
122 - pushHostContainer,
123 - pushHostContext,
124 - };
102 + pop(contextStackCursor, fiber);
103 + pop(contextFiberStackCursor, fiber);
104 }
105 +
106 +export {
107 + getHostContext,
108 + getRootHostContainer,
109 + popHostContainer,
110 + popHostContext,
111 + pushHostContainer,
112 + pushHostContext,
113 +};
packages/react-reconciler/src/ReactFiberHydrationContext.js
+309 -322
@@ -7,380 +7,367 @@
7 * @flow
8 */
9
10 -import type {HostConfig} from 'react-reconciler';
10 import type {Fiber} from './ReactFiber';
11 +import type {
12 + Instance,
13 + TextInstance,
14 + HydratableInstance,
15 + Container,
16 + HostContext,
17 +} from './ReactFiberHostConfig';
18
19 import {HostComponent, HostText, HostRoot} from 'shared/ReactTypeOfWork';
20 import {Deletion, Placement} from 'shared/ReactTypeOfSideEffect';
21 import invariant from 'fbjs/lib/invariant';
22
23 import {createFiberFromHostInstanceForDeletion} from './ReactFiber';
24 +import {
25 + shouldSetTextContent,
26 + supportsHydration,
27 + canHydrateInstance,
28 + canHydrateTextInstance,
29 + getNextHydratableSibling,
30 + getFirstHydratableChild,
31 + hydrateInstance,
32 + hydrateTextInstance,
33 + didNotMatchHydratedContainerTextInstance,
34 + didNotMatchHydratedTextInstance,
35 + didNotHydrateContainerInstance,
36 + didNotHydrateInstance,
37 + didNotFindHydratableContainerInstance,
38 + didNotFindHydratableContainerTextInstance,
39 + didNotFindHydratableInstance,
40 + didNotFindHydratableTextInstance,
41 +} from './ReactFiberHostConfig';
42
19 -export type HydrationContext<C, CX> = {
20 - enterHydrationState(fiber: Fiber): boolean,
21 - resetHydrationState(): void,
22 - tryToClaimNextHydratableInstance(fiber: Fiber): void,
23 - prepareToHydrateHostInstance(
24 - fiber: Fiber,
25 - rootContainerInstance: C,
26 - hostContext: CX,
27 - ): boolean,
28 - prepareToHydrateHostTextInstance(fiber: Fiber): boolean,
29 - popHydrationState(fiber: Fiber): boolean,
30 -};
31 -
32 -export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
33 - config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
34 -): HydrationContext<C, CX> {
35 - const {shouldSetTextContent, hydration} = config;
43 +// The deepest Fiber on the stack involved in a hydration context.
44 +// This may have been an insertion or a hydration.
45 +let hydrationParentFiber: null | Fiber = null;
46 +let nextHydratableInstance: null | HydratableInstance = null;
47 +let isHydrating: boolean = false;
48
37 - // If this doesn't have hydration mode.
38 - if (!hydration) {
39 - return {
40 - enterHydrationState() {
41 - return false;
42 - },
43 - resetHydrationState() {},
44 - tryToClaimNextHydratableInstance() {},
45 - prepareToHydrateHostInstance() {
46 - invariant(
47 - false,
48 - 'Expected prepareToHydrateHostInstance() to never be called. ' +
49 - 'This error is likely caused by a bug in React. Please file an issue.',
50 - );
51 - },
52 - prepareToHydrateHostTextInstance() {
53 - invariant(
54 - false,
55 - 'Expected prepareToHydrateHostTextInstance() to never be called. ' +
56 - 'This error is likely caused by a bug in React. Please file an issue.',
57 - );
58 - },
59 - popHydrationState(fiber: Fiber) {
60 - return false;
61 - },
62 - };
49 +function enterHydrationState(fiber: Fiber): boolean {
50 + if (!supportsHydration) {
51 + return false;
52 }
53
65 - const {
66 - canHydrateInstance,
67 - canHydrateTextInstance,
68 - getNextHydratableSibling,
69 - getFirstHydratableChild,
70 - hydrateInstance,
71 - hydrateTextInstance,
72 - didNotMatchHydratedContainerTextInstance,
73 - didNotMatchHydratedTextInstance,
74 - didNotHydrateContainerInstance,
75 - didNotHydrateInstance,
76 - didNotFindHydratableContainerInstance,
77 - didNotFindHydratableContainerTextInstance,
78 - didNotFindHydratableInstance,
79 - didNotFindHydratableTextInstance,
80 - } = hydration;
81 -
82 - // The deepest Fiber on the stack involved in a hydration context.
83 - // This may have been an insertion or a hydration.
84 - let hydrationParentFiber: null | Fiber = null;
85 - let nextHydratableInstance: null | HI = null;
86 - let isHydrating: boolean = false;
87 -
88 - function enterHydrationState(fiber: Fiber) {
89 - const parentInstance = fiber.stateNode.containerInfo;
90 - nextHydratableInstance = getFirstHydratableChild(parentInstance);
91 - hydrationParentFiber = fiber;
92 - isHydrating = true;
93 - return true;
94 - }
54 + const parentInstance = fiber.stateNode.containerInfo;
55 + nextHydratableInstance = getFirstHydratableChild(parentInstance);
56 + hydrationParentFiber = fiber;
57 + isHydrating = true;
58 + return true;
59 +}
60
96 - function deleteHydratableInstance(returnFiber: Fiber, instance: I | TI) {
97 - if (__DEV__) {
98 - switch (returnFiber.tag) {
99 - case HostRoot:
100 - didNotHydrateContainerInstance(
101 - returnFiber.stateNode.containerInfo,
102 - instance,
103 - );
104 - break;
105 - case HostComponent:
106 - didNotHydrateInstance(
107 - returnFiber.type,
108 - returnFiber.memoizedProps,
109 - returnFiber.stateNode,
110 - instance,
111 - );
112 - break;
113 - }
61 +function deleteHydratableInstance(
62 + returnFiber: Fiber,
63 + instance: HydratableInstance,
64 +) {
65 + if (__DEV__) {
66 + switch (returnFiber.tag) {
67 + case HostRoot:
68 + didNotHydrateContainerInstance(
69 + returnFiber.stateNode.containerInfo,
70 + instance,
71 + );
72 + break;
73 + case HostComponent:
74 + didNotHydrateInstance(
75 + returnFiber.type,
76 + returnFiber.memoizedProps,
77 + returnFiber.stateNode,
78 + instance,
79 + );
80 + break;
81 }
82 + }
83
116 - const childToDelete = createFiberFromHostInstanceForDeletion();
117 - childToDelete.stateNode = instance;
118 - childToDelete.return = returnFiber;
119 - childToDelete.effectTag = Deletion;
84 + const childToDelete = createFiberFromHostInstanceForDeletion();
85 + childToDelete.stateNode = instance;
86 + childToDelete.return = returnFiber;
87 + childToDelete.effectTag = Deletion;
88
121 - // This might seem like it belongs on progressedFirstDeletion. However,
122 - // these children are not part of the reconciliation list of children.
123 - // Even if we abort and rereconcile the children, that will try to hydrate
124 - // again and the nodes are still in the host tree so these will be
125 - // recreated.
126 - if (returnFiber.lastEffect !== null) {
127 - returnFiber.lastEffect.nextEffect = childToDelete;
128 - returnFiber.lastEffect = childToDelete;
129 - } else {
130 - returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
131 - }
89 + // This might seem like it belongs on progressedFirstDeletion. However,
90 + // these children are not part of the reconciliation list of children.
91 + // Even if we abort and rereconcile the children, that will try to hydrate
92 + // again and the nodes are still in the host tree so these will be
93 + // recreated.
94 + if (returnFiber.lastEffect !== null) {
95 + returnFiber.lastEffect.nextEffect = childToDelete;
96 + returnFiber.lastEffect = childToDelete;
97 + } else {
98 + returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
99 }
100 +}
101
134 - function insertNonHydratedInstance(returnFiber: Fiber, fiber: Fiber) {
135 - fiber.effectTag |= Placement;
136 - if (__DEV__) {
137 - switch (returnFiber.tag) {
138 - case HostRoot: {
139 - const parentContainer = returnFiber.stateNode.containerInfo;
140 - switch (fiber.tag) {
141 - case HostComponent:
142 - const type = fiber.type;
143 - const props = fiber.pendingProps;
144 - didNotFindHydratableContainerInstance(
145 - parentContainer,
146 - type,
147 - props,
148 - );
149 - break;
150 - case HostText:
151 - const text = fiber.pendingProps;
152 - didNotFindHydratableContainerTextInstance(parentContainer, text);
153 - break;
154 - }
155 - break;
102 +function insertNonHydratedInstance(returnFiber: Fiber, fiber: Fiber) {
103 + fiber.effectTag |= Placement;
104 + if (__DEV__) {
105 + switch (returnFiber.tag) {
106 + case HostRoot: {
107 + const parentContainer = returnFiber.stateNode.containerInfo;
108 + switch (fiber.tag) {
109 + case HostComponent:
110 + const type = fiber.type;
111 + const props = fiber.pendingProps;
112 + didNotFindHydratableContainerInstance(parentContainer, type, props);
113 + break;
114 + case HostText:
115 + const text = fiber.pendingProps;
116 + didNotFindHydratableContainerTextInstance(parentContainer, text);
117 + break;
118 }
157 - case HostComponent: {
158 - const parentType = returnFiber.type;
159 - const parentProps = returnFiber.memoizedProps;
160 - const parentInstance = returnFiber.stateNode;
161 - switch (fiber.tag) {
162 - case HostComponent:
163 - const type = fiber.type;
164 - const props = fiber.pendingProps;
165 - didNotFindHydratableInstance(
166 - parentType,
167 - parentProps,
168 - parentInstance,
169 - type,
170 - props,
171 - );
172 - break;
173 - case HostText:
174 - const text = fiber.pendingProps;
175 - didNotFindHydratableTextInstance(
176 - parentType,
177 - parentProps,
178 - parentInstance,
179 - text,
180 - );
181 - break;
182 - }
183 - break;
119 + break;
120 + }
121 + case HostComponent: {
122 + const parentType = returnFiber.type;
123 + const parentProps = returnFiber.memoizedProps;
124 + const parentInstance = returnFiber.stateNode;
125 + switch (fiber.tag) {
126 + case HostComponent:
127 + const type = fiber.type;
128 + const props = fiber.pendingProps;
129 + didNotFindHydratableInstance(
130 + parentType,
131 + parentProps,
132 + parentInstance,
133 + type,
134 + props,
135 + );
136 + break;
137 + case HostText:
138 + const text = fiber.pendingProps;
139 + didNotFindHydratableTextInstance(
140 + parentType,
141 + parentProps,
142 + parentInstance,
143 + text,
144 + );
145 + break;
146 }
185 - default:
186 - return;
147 + break;
148 }
149 + default:
150 + return;
151 }
152 }
153 +}
154
191 - function tryHydrate(fiber, nextInstance) {
192 - switch (fiber.tag) {
193 - case HostComponent: {
194 - const type = fiber.type;
195 - const props = fiber.pendingProps;
196 - const instance = canHydrateInstance(nextInstance, type, props);
197 - if (instance !== null) {
198 - fiber.stateNode = (instance: I);
199 - return true;
200 - }
201 - return false;
155 +function tryHydrate(fiber, nextInstance) {
156 + switch (fiber.tag) {
157 + case HostComponent: {
158 + const type = fiber.type;
159 + const props = fiber.pendingProps;
160 + const instance = canHydrateInstance(nextInstance, type, props);
161 + if (instance !== null) {
162 + fiber.stateNode = (instance: Instance);
163 + return true;
164 }
203 - case HostText: {
204 - const text = fiber.pendingProps;
205 - const textInstance = canHydrateTextInstance(nextInstance, text);
206 - if (textInstance !== null) {
207 - fiber.stateNode = (textInstance: TI);
208 - return true;
209 - }
210 - return false;
165 + return false;
166 + }
167 + case HostText: {
168 + const text = fiber.pendingProps;
169 + const textInstance = canHydrateTextInstance(nextInstance, text);
170 + if (textInstance !== null) {
171 + fiber.stateNode = (textInstance: TextInstance);
172 + return true;
173 }
212 - default:
213 - return false;
174 + return false;
175 }
176 + default:
177 + return false;
178 }
179 +}
180
217 - function tryToClaimNextHydratableInstance(fiber: Fiber) {
218 - if (!isHydrating) {
219 - return;
220 - }
221 - let nextInstance = nextHydratableInstance;
222 - if (!nextInstance) {
181 +function tryToClaimNextHydratableInstance(fiber: Fiber): void {
182 + if (!isHydrating) {
183 + return;
184 + }
185 + let nextInstance = nextHydratableInstance;
186 + if (!nextInstance) {
187 + // Nothing to hydrate. Make it an insertion.
188 + insertNonHydratedInstance((hydrationParentFiber: any), fiber);
189 + isHydrating = false;
190 + hydrationParentFiber = fiber;
191 + return;
192 + }
193 + const firstAttemptedInstance = nextInstance;
194 + if (!tryHydrate(fiber, nextInstance)) {
195 + // If we can't hydrate this instance let's try the next one.
196 + // We use this as a heuristic. It's based on intuition and not data so it
197 + // might be flawed or unnecessary.
198 + nextInstance = getNextHydratableSibling(firstAttemptedInstance);
199 + if (!nextInstance || !tryHydrate(fiber, nextInstance)) {
200 // Nothing to hydrate. Make it an insertion.
201 insertNonHydratedInstance((hydrationParentFiber: any), fiber);
202 isHydrating = false;
203 hydrationParentFiber = fiber;
204 return;
205 }
229 - if (!tryHydrate(fiber, nextInstance)) {
230 - // If we can't hydrate this instance let's try the next one.
231 - // We use this as a heuristic. It's based on intuition and not data so it
232 - // might be flawed or unnecessary.
233 - nextInstance = getNextHydratableSibling(nextInstance);
234 - if (!nextInstance || !tryHydrate(fiber, nextInstance)) {
235 - // Nothing to hydrate. Make it an insertion.
236 - insertNonHydratedInstance((hydrationParentFiber: any), fiber);
237 - isHydrating = false;
238 - hydrationParentFiber = fiber;
239 - return;
240 - }
241 - // We matched the next one, we'll now assume that the first one was
242 - // superfluous and we'll delete it. Since we can't eagerly delete it
243 - // we'll have to schedule a deletion. To do that, this node needs a dummy
244 - // fiber associated with it.
245 - deleteHydratableInstance(
246 - (hydrationParentFiber: any),
247 - nextHydratableInstance,
248 - );
249 - }
250 - hydrationParentFiber = fiber;
251 - nextHydratableInstance = getFirstHydratableChild(nextInstance);
206 + // We matched the next one, we'll now assume that the first one was
207 + // superfluous and we'll delete it. Since we can't eagerly delete it
208 + // we'll have to schedule a deletion. To do that, this node needs a dummy
209 + // fiber associated with it.
210 + deleteHydratableInstance(
211 + (hydrationParentFiber: any),
212 + firstAttemptedInstance,
213 + );
214 }
215 + hydrationParentFiber = fiber;
216 + nextHydratableInstance = getFirstHydratableChild((nextInstance: any));
217 +}
218
254 - function prepareToHydrateHostInstance(
255 - fiber: Fiber,
256 - rootContainerInstance: C,
257 - hostContext: CX,
258 - ): boolean {
259 - const instance: I = fiber.stateNode;
260 - const updatePayload = hydrateInstance(
261 - instance,
262 - fiber.type,
263 - fiber.memoizedProps,
264 - rootContainerInstance,
265 - hostContext,
266 - fiber,
219 +function prepareToHydrateHostInstance(
220 + fiber: Fiber,
221 + rootContainerInstance: Container,
222 + hostContext: HostContext,
223 +): boolean {
224 + if (!supportsHydration) {
225 + invariant(
226 + false,
227 + 'Expected prepareToHydrateHostInstance() to never be called. ' +
228 + 'This error is likely caused by a bug in React. Please file an issue.',
229 );
268 - // TODO: Type this specific to this type of component.
269 - fiber.updateQueue = (updatePayload: any);
270 - // If the update payload indicates that there is a change or if there
271 - // is a new ref we mark this as an update.
272 - if (updatePayload !== null) {
273 - return true;
274 - }
275 - return false;
230 }
231
278 - function prepareToHydrateHostTextInstance(fiber: Fiber): boolean {
279 - const textInstance: TI = fiber.stateNode;
280 - const textContent: string = fiber.memoizedProps;
281 - const shouldUpdate = hydrateTextInstance(textInstance, textContent, fiber);
282 - if (__DEV__) {
283 - if (shouldUpdate) {
284 - // We assume that prepareToHydrateHostTextInstance is called in a context where the
285 - // hydration parent is the parent host component of this host text.
286 - const returnFiber = hydrationParentFiber;
287 - if (returnFiber !== null) {
288 - switch (returnFiber.tag) {
289 - case HostRoot: {
290 - const parentContainer = returnFiber.stateNode.containerInfo;
291 - didNotMatchHydratedContainerTextInstance(
292 - parentContainer,
293 - textInstance,
294 - textContent,
295 - );
296 - break;
297 - }
298 - case HostComponent: {
299 - const parentType = returnFiber.type;
300 - const parentProps = returnFiber.memoizedProps;
301 - const parentInstance = returnFiber.stateNode;
302 - didNotMatchHydratedTextInstance(
303 - parentType,
304 - parentProps,
305 - parentInstance,
306 - textInstance,
307 - textContent,
308 - );
309 - break;
310 - }
232 + const instance: Instance = fiber.stateNode;
233 + const updatePayload = hydrateInstance(
234 + instance,
235 + fiber.type,
236 + fiber.memoizedProps,
237 + rootContainerInstance,
238 + hostContext,
239 + fiber,
240 + );
241 + // TODO: Type this specific to this type of component.
242 + fiber.updateQueue = (updatePayload: any);
243 + // If the update payload indicates that there is a change or if there
244 + // is a new ref we mark this as an update.
245 + if (updatePayload !== null) {
246 + return true;
247 + }
248 + return false;
249 +}
250 +
251 +function prepareToHydrateHostTextInstance(fiber: Fiber): boolean {
252 + if (!supportsHydration) {
253 + invariant(
254 + false,
255 + 'Expected prepareToHydrateHostTextInstance() to never be called. ' +
256 + 'This error is likely caused by a bug in React. Please file an issue.',
257 + );
258 + }
259 +
260 + const textInstance: TextInstance = fiber.stateNode;
261 + const textContent: string = fiber.memoizedProps;
262 + const shouldUpdate = hydrateTextInstance(textInstance, textContent, fiber);
263 + if (__DEV__) {
264 + if (shouldUpdate) {
265 + // We assume that prepareToHydrateHostTextInstance is called in a context where the
266 + // hydration parent is the parent host component of this host text.
267 + const returnFiber = hydrationParentFiber;
268 + if (returnFiber !== null) {
269 + switch (returnFiber.tag) {
270 + case HostRoot: {
271 + const parentContainer = returnFiber.stateNode.containerInfo;
272 + didNotMatchHydratedContainerTextInstance(
273 + parentContainer,
274 + textInstance,
275 + textContent,
276 + );
277 + break;
278 + }
279 + case HostComponent: {
280 + const parentType = returnFiber.type;
281 + const parentProps = returnFiber.memoizedProps;
282 + const parentInstance = returnFiber.stateNode;
283 + didNotMatchHydratedTextInstance(
284 + parentType,
285 + parentProps,
286 + parentInstance,
287 + textInstance,
288 + textContent,
289 + );
290 + break;
291 }
292 }
293 }
294 }
315 - return shouldUpdate;
295 }
296 + return shouldUpdate;
297 +}
298
318 - function popToNextHostParent(fiber: Fiber): void {
319 - let parent = fiber.return;
320 - while (
321 - parent !== null &&
322 - parent.tag !== HostComponent &&
323 - parent.tag !== HostRoot
324 - ) {
325 - parent = parent.return;
326 - }
327 - hydrationParentFiber = parent;
299 +function popToNextHostParent(fiber: Fiber): void {
300 + let parent = fiber.return;
301 + while (
302 + parent !== null &&
303 + parent.tag !== HostComponent &&
304 + parent.tag !== HostRoot
305 + ) {
306 + parent = parent.return;
307 }
308 + hydrationParentFiber = parent;
309 +}
310
330 - function popHydrationState(fiber: Fiber): boolean {
331 - if (fiber !== hydrationParentFiber) {
332 - // We're deeper than the current hydration context, inside an inserted
333 - // tree.
334 - return false;
335 - }
336 - if (!isHydrating) {
337 - // If we're not currently hydrating but we're in a hydration context, then
338 - // we were an insertion and now need to pop up reenter hydration of our
339 - // siblings.
340 - popToNextHostParent(fiber);
341 - isHydrating = true;
342 - return false;
343 - }
311 +function popHydrationState(fiber: Fiber): boolean {
312 + if (!supportsHydration) {
313 + return false;
314 + }
315 + if (fiber !== hydrationParentFiber) {
316 + // We're deeper than the current hydration context, inside an inserted
317 + // tree.
318 + return false;
319 + }
320 + if (!isHydrating) {
321 + // If we're not currently hydrating but we're in a hydration context, then
322 + // we were an insertion and now need to pop up reenter hydration of our
323 + // siblings.
324 + popToNextHostParent(fiber);
325 + isHydrating = true;
326 + return false;
327 + }
328
345 - const type = fiber.type;
329 + const type = fiber.type;
330
347 - // If we have any remaining hydratable nodes, we need to delete them now.
348 - // We only do this deeper than head and body since they tend to have random
349 - // other nodes in them. We also ignore components with pure text content in
350 - // side of them.
351 - // TODO: Better heuristic.
352 - if (
353 - fiber.tag !== HostComponent ||
354 - (type !== 'head' &&
355 - type !== 'body' &&
356 - !shouldSetTextContent(type, fiber.memoizedProps))
357 - ) {
358 - let nextInstance = nextHydratableInstance;
359 - while (nextInstance) {
360 - deleteHydratableInstance(fiber, nextInstance);
361 - nextInstance = getNextHydratableSibling(nextInstance);
362 - }
331 + // If we have any remaining hydratable nodes, we need to delete them now.
332 + // We only do this deeper than head and body since they tend to have random
333 + // other nodes in them. We also ignore components with pure text content in
334 + // side of them.
335 + // TODO: Better heuristic.
336 + if (
337 + fiber.tag !== HostComponent ||
338 + (type !== 'head' &&
339 + type !== 'body' &&
340 + !shouldSetTextContent(type, fiber.memoizedProps))
341 + ) {
342 + let nextInstance = nextHydratableInstance;
343 + while (nextInstance) {
344 + deleteHydratableInstance(fiber, nextInstance);
345 + nextInstance = getNextHydratableSibling(nextInstance);
346 }
364 -
365 - popToNextHostParent(fiber);
366 - nextHydratableInstance = hydrationParentFiber
367 - ? getNextHydratableSibling(fiber.stateNode)
368 - : null;
369 - return true;
347 }
348
372 - function resetHydrationState() {
373 - hydrationParentFiber = null;
374 - nextHydratableInstance = null;
375 - isHydrating = false;
349 + popToNextHostParent(fiber);
350 + nextHydratableInstance = hydrationParentFiber
351 + ? getNextHydratableSibling(fiber.stateNode)
352 + : null;
353 + return true;
354 +}
355 +
356 +function resetHydrationState(): void {
357 + if (!supportsHydration) {
358 + return;
359 }
360
378 - return {
379 - enterHydrationState,
380 - resetHydrationState,
381 - tryToClaimNextHydratableInstance,
382 - prepareToHydrateHostInstance,
383 - prepareToHydrateHostTextInstance,
384 - popHydrationState,
385 - };
361 + hydrationParentFiber = null;
362 + nextHydratableInstance = null;
363 + isHydrating = false;
364 }
365 +
366 +export {
367 + enterHydrationState,
368 + resetHydrationState,
369 + tryToClaimNextHydratableInstance,
370 + prepareToHydrateHostInstance,
371 + prepareToHydrateHostTextInstance,
372 + popHydrationState,
373 +};
packages/react-reconciler/src/ReactFiberNewContext.js
+75 -77
@@ -9,7 +9,7 @@
9
10 import type {Fiber} from './ReactFiber';
11 import type {ReactContext} from 'shared/ReactTypes';
12 -import type {StackCursor, Stack} from './ReactFiberStack';
12 +import type {StackCursor} from './ReactFiberStack';
13
14 export type NewContext = {
15 pushProvider(providerFiber: Fiber): void,
@@ -19,90 +19,88 @@ export type NewContext = {
19 };
20
21 import warning from 'fbjs/lib/warning';
22 +import {isPrimaryRenderer} from './ReactFiberHostConfig';
23 +import {createCursor, push, pop} from './ReactFiberStack';
24
23 -export default function(stack: Stack, isPrimaryRenderer: boolean) {
24 - const {createCursor, push, pop} = stack;
25 +const providerCursor: StackCursor<Fiber | null> = createCursor(null);
26 +const valueCursor: StackCursor<mixed> = createCursor(null);
27 +const changedBitsCursor: StackCursor<number> = createCursor(0);
28
26 - const providerCursor: StackCursor<Fiber | null> = createCursor(null);
27 - const valueCursor: StackCursor<mixed> = createCursor(null);
28 - const changedBitsCursor: StackCursor<number> = createCursor(0);
29 -
30 - let rendererSigil;
31 - if (__DEV__) {
32 - // Use this to detect multiple renderers using the same context
33 - rendererSigil = {};
34 - }
35 -
36 - function pushProvider(providerFiber: Fiber): void {
37 - const context: ReactContext<any> = providerFiber.type._context;
38 -
39 - if (isPrimaryRenderer) {
40 - push(changedBitsCursor, context._changedBits, providerFiber);
41 - push(valueCursor, context._currentValue, providerFiber);
42 - push(providerCursor, providerFiber, providerFiber);
43 -
44 - context._currentValue = providerFiber.pendingProps.value;
45 - context._changedBits = providerFiber.stateNode;
46 - if (__DEV__) {
47 - warning(
48 - context._currentRenderer === undefined ||
49 - context._currentRenderer === null ||
50 - context._currentRenderer === rendererSigil,
51 - 'Detected multiple renderers concurrently rendering the ' +
52 - 'same context provider. This is currently unsupported.',
53 - );
54 - context._currentRenderer = rendererSigil;
55 - }
56 - } else {
57 - push(changedBitsCursor, context._changedBits2, providerFiber);
58 - push(valueCursor, context._currentValue2, providerFiber);
59 - push(providerCursor, providerFiber, providerFiber);
29 +let rendererSigil;
30 +if (__DEV__) {
31 + // Use this to detect multiple renderers using the same context
32 + rendererSigil = {};
33 +}
34
61 - context._currentValue2 = providerFiber.pendingProps.value;
62 - context._changedBits2 = providerFiber.stateNode;
63 - if (__DEV__) {
64 - warning(
65 - context._currentRenderer2 === undefined ||
66 - context._currentRenderer2 === null ||
67 - context._currentRenderer2 === rendererSigil,
68 - 'Detected multiple renderers concurrently rendering the ' +
69 - 'same context provider. This is currently unsupported.',
70 - );
71 - context._currentRenderer2 = rendererSigil;
72 - }
35 +function pushProvider(providerFiber: Fiber): void {
36 + const context: ReactContext<any> = providerFiber.type._context;
37 +
38 + if (isPrimaryRenderer) {
39 + push(changedBitsCursor, context._changedBits, providerFiber);
40 + push(valueCursor, context._currentValue, providerFiber);
41 + push(providerCursor, providerFiber, providerFiber);
42 +
43 + context._currentValue = providerFiber.pendingProps.value;
44 + context._changedBits = providerFiber.stateNode;
45 + if (__DEV__) {
46 + warning(
47 + context._currentRenderer === undefined ||
48 + context._currentRenderer === null ||
49 + context._currentRenderer === rendererSigil,
50 + 'Detected multiple renderers concurrently rendering the ' +
51 + 'same context provider. This is currently unsupported.',
52 + );
53 + context._currentRenderer = rendererSigil;
54 }
74 - }
75 -
76 - function popProvider(providerFiber: Fiber): void {
77 - const changedBits = changedBitsCursor.current;
78 - const currentValue = valueCursor.current;
79 -
80 - pop(providerCursor, providerFiber);
81 - pop(valueCursor, providerFiber);
82 - pop(changedBitsCursor, providerFiber);
83 -
84 - const context: ReactContext<any> = providerFiber.type._context;
85 - if (isPrimaryRenderer) {
86 - context._currentValue = currentValue;
87 - context._changedBits = changedBits;
88 - } else {
89 - context._currentValue2 = currentValue;
90 - context._changedBits2 = changedBits;
55 + } else {
56 + push(changedBitsCursor, context._changedBits2, providerFiber);
57 + push(valueCursor, context._currentValue2, providerFiber);
58 + push(providerCursor, providerFiber, providerFiber);
59 +
60 + context._currentValue2 = providerFiber.pendingProps.value;
61 + context._changedBits2 = providerFiber.stateNode;
62 + if (__DEV__) {
63 + warning(
64 + context._currentRenderer2 === undefined ||
65 + context._currentRenderer2 === null ||
66 + context._currentRenderer2 === rendererSigil,
67 + 'Detected multiple renderers concurrently rendering the ' +
68 + 'same context provider. This is currently unsupported.',
69 + );
70 + context._currentRenderer2 = rendererSigil;
71 }
72 }
73 +}
74
94 - function getContextCurrentValue(context: ReactContext<any>): any {
95 - return isPrimaryRenderer ? context._currentValue : context._currentValue2;
75 +function popProvider(providerFiber: Fiber): void {
76 + const changedBits = changedBitsCursor.current;
77 + const currentValue = valueCursor.current;
78 +
79 + pop(providerCursor, providerFiber);
80 + pop(valueCursor, providerFiber);
81 + pop(changedBitsCursor, providerFiber);
82 +
83 + const context: ReactContext<any> = providerFiber.type._context;
84 + if (isPrimaryRenderer) {
85 + context._currentValue = currentValue;
86 + context._changedBits = changedBits;
87 + } else {
88 + context._currentValue2 = currentValue;
89 + context._changedBits2 = changedBits;
90 }
91 +}
92
98 - function getContextChangedBits(context: ReactContext<any>): number {
99 - return isPrimaryRenderer ? context._changedBits : context._changedBits2;
100 - }
93 +function getContextCurrentValue(context: ReactContext<any>): any {
94 + return isPrimaryRenderer ? context._currentValue : context._currentValue2;
95 +}
96
102 - return {
103 - pushProvider,
104 - popProvider,
105 - getContextCurrentValue,
106 - getContextChangedBits,
107 - };
97 +function getContextChangedBits(context: ReactContext<any>): number {
98 + return isPrimaryRenderer ? context._changedBits : context._changedBits2;
99 }
100 +
101 +export {
102 + pushProvider,
103 + popProvider,
104 + getContextCurrentValue,
105 + getContextChangedBits,
106 +};
packages/react-reconciler/src/ReactFiberReconciler.js
+200 -448
@@ -9,6 +9,12 @@
9
10 import type {Fiber} from './ReactFiber';
11 import type {FiberRoot} from './ReactFiberRoot';
12 +import type {
13 + Instance,
14 + TextInstance,
15 + Container,
16 + PublicInstance,
17 +} from './ReactFiberHostConfig';
18 import type {ReactNodeList} from 'shared/ReactTypes';
19 import type {ExpirationTime} from './ReactFiberExpirationTime';
20
@@ -23,508 +29,254 @@ import getComponentName from 'shared/getComponentName';
29 import invariant from 'fbjs/lib/invariant';
30 import warning from 'fbjs/lib/warning';
31
32 +import {getPublicInstance} from './ReactFiberHostConfig';
33 +import {
34 + findCurrentUnmaskedContext,
35 + isContextProvider,
36 + processChildContext,
37 +} from './ReactFiberContext';
38 import {createFiberRoot} from './ReactFiberRoot';
39 import * as ReactFiberDevToolsHook from './ReactFiberDevToolsHook';
28 -import ReactFiberScheduler from './ReactFiberScheduler';
40 +import {
41 + computeUniqueAsyncExpiration,
42 + recalculateCurrentTime,
43 + computeExpirationForFiber,
44 + scheduleWork,
45 + requestWork,
46 + flushRoot,
47 + batchedUpdates,
48 + unbatchedUpdates,
49 + flushSync,
50 + flushControlled,
51 + deferredUpdates,
52 + syncUpdates,
53 + interactiveUpdates,
54 + flushInteractiveUpdates,
55 +} from './ReactFiberScheduler';
56 import {createUpdate, enqueueUpdate} from './ReactUpdateQueue';
57 import ReactFiberInstrumentation from './ReactFiberInstrumentation';
58 import ReactDebugCurrentFiber from './ReactDebugCurrentFiber';
59
33 -let didWarnAboutNestedUpdates;
34 -
35 -if (__DEV__) {
36 - didWarnAboutNestedUpdates = false;
37 -}
38 -
39 -export type Deadline = {
40 - timeRemaining: () => number,
41 - didTimeout: boolean,
42 -};
43 -
44 -type OpaqueHandle = Fiber;
60 type OpaqueRoot = FiberRoot;
61
47 -export type HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL> = {
48 - getRootHostContext(rootContainerInstance: C): CX,
49 - getChildHostContext(parentHostContext: CX, type: T, instance: C): CX,
50 - getPublicInstance(instance: I | TI): PI,
51 -
52 - createInstance(
53 - type: T,
54 - props: P,
55 - rootContainerInstance: C,
56 - hostContext: CX,
57 - internalInstanceHandle: OpaqueHandle,
58 - ): I,
59 - appendInitialChild(parentInstance: I, child: I | TI): void,
60 - finalizeInitialChildren(
61 - parentInstance: I,
62 - type: T,
63 - props: P,
64 - rootContainerInstance: C,
65 - hostContext: CX,
66 - ): boolean,
67 -
68 - prepareUpdate(
69 - instance: I,
70 - type: T,
71 - oldProps: P,
72 - newProps: P,
73 - rootContainerInstance: C,
74 - hostContext: CX,
75 - ): null | PL,
76 -
77 - shouldSetTextContent(type: T, props: P): boolean,
78 - shouldDeprioritizeSubtree(type: T, props: P): boolean,
79 -
80 - createTextInstance(
81 - text: string,
82 - rootContainerInstance: C,
83 - hostContext: CX,
84 - internalInstanceHandle: OpaqueHandle,
85 - ): TI,
86 -
87 - scheduleDeferredCallback(
88 - callback: (deadline: Deadline) => void,
89 - options?: {timeout: number},
90 - ): number,
91 - cancelDeferredCallback(callbackID: number): void,
92 -
93 - prepareForCommit(containerInfo: C): void,
94 - resetAfterCommit(containerInfo: C): void,
95 -
96 - now(): number,
97 -
98 - // Temporary workaround for scenario where multiple renderers concurrently
99 - // render using the same context objects. E.g. React DOM and React ART on the
100 - // same page. DOM is the primary renderer; ART is the secondary renderer.
101 - isPrimaryRenderer: boolean,
102 -
103 - +hydration?: HydrationHostConfig<T, P, I, TI, HI, C, CX, PL>,
104 -
105 - +mutation?: MutableUpdatesHostConfig<T, P, I, TI, C, PL>,
106 - +persistence?: PersistentUpdatesHostConfig<T, P, I, TI, C, CC, PL>,
107 -};
108 -
109 -type MutableUpdatesHostConfig<T, P, I, TI, C, PL> = {
110 - commitUpdate(
111 - instance: I,
112 - updatePayload: PL,
113 - type: T,
114 - oldProps: P,
115 - newProps: P,
116 - internalInstanceHandle: OpaqueHandle,
117 - ): void,
118 - commitMount(
119 - instance: I,
120 - type: T,
121 - newProps: P,
122 - internalInstanceHandle: OpaqueHandle,
123 - ): void,
124 - commitTextUpdate(textInstance: TI, oldText: string, newText: string): void,
125 - resetTextContent(instance: I): void,
126 - appendChild(parentInstance: I, child: I | TI): void,
127 - appendChildToContainer(container: C, child: I | TI): void,
128 - insertBefore(parentInstance: I, child: I | TI, beforeChild: I | TI): void,
129 - insertInContainerBefore(
130 - container: C,
131 - child: I | TI,
132 - beforeChild: I | TI,
133 - ): void,
134 - removeChild(parentInstance: I, child: I | TI): void,
135 - removeChildFromContainer(container: C, child: I | TI): void,
136 -};
137 -
138 -type PersistentUpdatesHostConfig<T, P, I, TI, C, CC, PL> = {
139 - cloneInstance(
140 - instance: I,
141 - updatePayload: null | PL,
142 - type: T,
143 - oldProps: P,
144 - newProps: P,
145 - internalInstanceHandle: OpaqueHandle,
146 - keepChildren: boolean,
147 - recyclableInstance: I,
148 - ): I,
149 -
150 - createContainerChildSet(container: C): CC,
151 -
152 - appendChildToContainerChildSet(childSet: CC, child: I | TI): void,
153 - finalizeContainerChildren(container: C, newChildren: CC): void,
154 -
155 - replaceContainerChildren(container: C, newChildren: CC): void,
156 -};
157 -
158 -type HydrationHostConfig<T, P, I, TI, HI, C, CX, PL> = {
159 - // Optional hydration
160 - canHydrateInstance(instance: HI, type: T, props: P): null | I,
161 - canHydrateTextInstance(instance: HI, text: string): null | TI,
162 - getNextHydratableSibling(instance: I | TI | HI): null | HI,
163 - getFirstHydratableChild(parentInstance: I | C): null | HI,
164 - hydrateInstance(
165 - instance: I,
166 - type: T,
167 - props: P,
168 - rootContainerInstance: C,
169 - hostContext: CX,
170 - internalInstanceHandle: OpaqueHandle,
171 - ): null | PL,
172 - hydrateTextInstance(
173 - textInstance: TI,
174 - text: string,
175 - internalInstanceHandle: OpaqueHandle,
176 - ): boolean,
177 - didNotMatchHydratedContainerTextInstance(
178 - parentContainer: C,
179 - textInstance: TI,
180 - text: string,
181 - ): void,
182 - didNotMatchHydratedTextInstance(
183 - parentType: T,
184 - parentProps: P,
185 - parentInstance: I,
186 - textInstance: TI,
187 - text: string,
188 - ): void,
189 - didNotHydrateContainerInstance(parentContainer: C, instance: I | TI): void,
190 - didNotHydrateInstance(
191 - parentType: T,
192 - parentProps: P,
193 - parentInstance: I,
194 - instance: I | TI,
195 - ): void,
196 - didNotFindHydratableContainerInstance(
197 - parentContainer: C,
198 - type: T,
199 - props: P,
200 - ): void,
201 - didNotFindHydratableContainerTextInstance(
202 - parentContainer: C,
203 - text: string,
204 - ): void,
205 - didNotFindHydratableInstance(
206 - parentType: T,
207 - parentProps: P,
208 - parentInstance: I,
209 - type: T,
210 - props: P,
211 - ): void,
212 - didNotFindHydratableTextInstance(
213 - parentType: T,
214 - parentProps: P,
215 - parentInstance: I,
216 - text: string,
217 - ): void,
218 -};
219 -
62 // 0 is PROD, 1 is DEV.
63 // Might add PROFILE later.
64 type BundleType = 0 | 1;
65
224 -type DevToolsConfig<I, TI> = {|
66 +type DevToolsConfig = {|
67 bundleType: BundleType,
68 version: string,
69 rendererPackageName: string,
70 // Note: this actually *does* depend on Fiber internal fields.
71 // Used by "inspect clicked DOM element" in React DevTools.
230 - findFiberByHostInstance?: (instance: I | TI) => Fiber,
72 + findFiberByHostInstance?: (instance: Instance | TextInstance) => Fiber,
73 // Used by RN in-app inspector.
74 // This API is unfortunately RN-specific.
75 // TODO: Change it to accept Fiber instead and type it properly.
76 getInspectorDataForViewTag?: (tag: number) => Object,
77 |};
78
237 -export type Reconciler<C, I, TI> = {
238 - createContainer(
239 - containerInfo: C,
240 - isAsync: boolean,
241 - hydrate: boolean,
242 - ): OpaqueRoot,
243 - updateContainer(
244 - element: ReactNodeList,
245 - container: OpaqueRoot,
246 - parentComponent: ?React$Component<any, any>,
247 - callback: ?Function,
248 - ): ExpirationTime,
249 - updateContainerAtExpirationTime(
250 - element: ReactNodeList,
251 - container: OpaqueRoot,
252 - parentComponent: ?React$Component<any, any>,
253 - expirationTime: ExpirationTime,
254 - callback: ?Function,
255 - ): ExpirationTime,
256 - flushRoot(root: OpaqueRoot, expirationTime: ExpirationTime): void,
257 - requestWork(root: OpaqueRoot, expirationTime: ExpirationTime): void,
258 - batchedUpdates<A>(fn: () => A): A,
259 - unbatchedUpdates<A>(fn: () => A): A,
260 - flushSync<A>(fn: () => A): A,
261 - flushControlled(fn: () => mixed): void,
262 - deferredUpdates<A>(fn: () => A): A,
263 - interactiveUpdates<A>(fn: () => A): A,
264 - injectIntoDevTools(devToolsConfig: DevToolsConfig<I, TI>): boolean,
265 - computeUniqueAsyncExpiration(): ExpirationTime,
266 -
267 - // Used to extract the return value from the initial render. Legacy API.
268 - getPublicRootInstance(
269 - container: OpaqueRoot,
270 - ): React$Component<any, any> | TI | I | null,
271 -
272 - // Use for findDOMNode/findHostNode. Legacy API.
273 - findHostInstance(component: Object): I | TI | null,
274 -
275 - // Used internally for filtering out portals. Legacy API.
276 - findHostInstanceWithNoPortals(component: Fiber): I | TI | null,
277 -};
278 -
279 -export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
280 - config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
281 -): Reconciler<C, I, TI> {
282 - const {getPublicInstance} = config;
283 -
284 - const {
285 - computeUniqueAsyncExpiration,
286 - recalculateCurrentTime,
287 - computeExpirationForFiber,
288 - scheduleWork,
289 - requestWork,
290 - flushRoot,
291 - batchedUpdates,
292 - unbatchedUpdates,
293 - flushSync,
294 - flushControlled,
295 - deferredUpdates,
296 - syncUpdates,
297 - interactiveUpdates,
298 - flushInteractiveUpdates,
299 - legacyContext,
300 - } = ReactFiberScheduler(config);
301 -
302 - const {
303 - findCurrentUnmaskedContext,
304 - isContextProvider,
305 - processChildContext,
306 - } = legacyContext;
79 +let didWarnAboutNestedUpdates;
80
308 - function getContextForSubtree(
309 - parentComponent: ?React$Component<any, any>,
310 - ): Object {
311 - if (!parentComponent) {
312 - return emptyObject;
313 - }
81 +if (__DEV__) {
82 + didWarnAboutNestedUpdates = false;
83 +}
84
315 - const fiber = ReactInstanceMap.get(parentComponent);
316 - const parentContext = findCurrentUnmaskedContext(fiber);
317 - return isContextProvider(fiber)
318 - ? processChildContext(fiber, parentContext)
319 - : parentContext;
85 +function getContextForSubtree(
86 + parentComponent: ?React$Component<any, any>,
87 +): Object {
88 + if (!parentComponent) {
89 + return emptyObject;
90 }
91
322 - function scheduleRootUpdate(
323 - current: Fiber,
324 - element: ReactNodeList,
325 - expirationTime: ExpirationTime,
326 - callback: ?Function,
327 - ) {
328 - if (__DEV__) {
329 - if (
330 - ReactDebugCurrentFiber.phase === 'render' &&
331 - ReactDebugCurrentFiber.current !== null &&
332 - !didWarnAboutNestedUpdates
333 - ) {
334 - didWarnAboutNestedUpdates = true;
335 - warning(
336 - false,
337 - 'Render methods should be a pure function of props and state; ' +
338 - 'triggering nested component updates from render is not allowed. ' +
339 - 'If necessary, trigger nested updates in componentDidUpdate.\n\n' +
340 - 'Check the render method of %s.',
341 - getComponentName(ReactDebugCurrentFiber.current) || 'Unknown',
342 - );
343 - }
344 - }
345 -
346 - const update = createUpdate(expirationTime);
347 - // Caution: React DevTools currently depends on this property
348 - // being called "element".
349 - update.payload = {element};
92 + const fiber = ReactInstanceMap.get(parentComponent);
93 + const parentContext = findCurrentUnmaskedContext(fiber);
94 + return isContextProvider(fiber)
95 + ? processChildContext(fiber, parentContext)
96 + : parentContext;
97 +}
98
351 - callback = callback === undefined ? null : callback;
352 - if (callback !== null) {
99 +function scheduleRootUpdate(
100 + current: Fiber,
101 + element: ReactNodeList,
102 + expirationTime: ExpirationTime,
103 + callback: ?Function,
104 +) {
105 + if (__DEV__) {
106 + if (
107 + ReactDebugCurrentFiber.phase === 'render' &&
108 + ReactDebugCurrentFiber.current !== null &&
109 + !didWarnAboutNestedUpdates
110 + ) {
111 + didWarnAboutNestedUpdates = true;
112 warning(
354 - typeof callback === 'function',
355 - 'render(...): Expected the last optional `callback` argument to be a ' +
356 - 'function. Instead received: %s.',
357 - callback,
113 + false,
114 + 'Render methods should be a pure function of props and state; ' +
115 + 'triggering nested component updates from render is not allowed. ' +
116 + 'If necessary, trigger nested updates in componentDidUpdate.\n\n' +
117 + 'Check the render method of %s.',
118 + getComponentName(ReactDebugCurrentFiber.current) || 'Unknown',
119 );
359 - update.callback = callback;
120 }
361 - enqueueUpdate(current, update, expirationTime);
362 -
363 - scheduleWork(current, expirationTime);
364 - return expirationTime;
121 }
122
367 - function updateContainerAtExpirationTime(
368 - element: ReactNodeList,
369 - container: OpaqueRoot,
370 - parentComponent: ?React$Component<any, any>,
371 - expirationTime: ExpirationTime,
372 - callback: ?Function,
373 - ) {
374 - // TODO: If this is a nested container, this won't be the root.
375 - const current = container.current;
376 -
377 - if (__DEV__) {
378 - if (ReactFiberInstrumentation.debugTool) {
379 - if (current.alternate === null) {
380 - ReactFiberInstrumentation.debugTool.onMountContainer(container);
381 - } else if (element === null) {
382 - ReactFiberInstrumentation.debugTool.onUnmountContainer(container);
383 - } else {
384 - ReactFiberInstrumentation.debugTool.onUpdateContainer(container);
385 - }
386 - }
387 - }
388 -
389 - const context = getContextForSubtree(parentComponent);
390 - if (container.context === null) {
391 - container.context = context;
392 - } else {
393 - container.pendingContext = context;
394 - }
395 -
396 - return scheduleRootUpdate(current, element, expirationTime, callback);
123 + const update = createUpdate(expirationTime);
124 + // Caution: React DevTools currently depends on this property
125 + // being called "element".
126 + update.payload = {element};
127 +
128 + callback = callback === undefined ? null : callback;
129 + if (callback !== null) {
130 + warning(
131 + typeof callback === 'function',
132 + 'render(...): Expected the last optional `callback` argument to be a ' +
133 + 'function. Instead received: %s.',
134 + callback,
135 + );
136 + update.callback = callback;
137 }
138 + enqueueUpdate(current, update, expirationTime);
139
399 - function findHostInstance(component: Object): PI | null {
400 - const fiber = ReactInstanceMap.get(component);
401 - if (fiber === undefined) {
402 - if (typeof component.render === 'function') {
403 - invariant(false, 'Unable to find node on an unmounted component.');
140 + scheduleWork(current, expirationTime);
141 + return expirationTime;
142 +}
143 +
144 +export function updateContainerAtExpirationTime(
145 + element: ReactNodeList,
146 + container: OpaqueRoot,
147 + parentComponent: ?React$Component<any, any>,
148 + expirationTime: ExpirationTime,
149 + callback: ?Function,
150 +) {
151 + // TODO: If this is a nested container, this won't be the root.
152 + const current = container.current;
153 +
154 + if (__DEV__) {
155 + if (ReactFiberInstrumentation.debugTool) {
156 + if (current.alternate === null) {
157 + ReactFiberInstrumentation.debugTool.onMountContainer(container);
158 + } else if (element === null) {
159 + ReactFiberInstrumentation.debugTool.onUnmountContainer(container);
160 } else {
405 - invariant(
406 - false,
407 - 'Argument appears to not be a ReactComponent. Keys: %s',
408 - Object.keys(component),
409 - );
161 + ReactFiberInstrumentation.debugTool.onUpdateContainer(container);
162 }
163 }
412 - const hostFiber = findCurrentHostFiber(fiber);
413 - if (hostFiber === null) {
414 - return null;
415 - }
416 - return hostFiber.stateNode;
164 }
165
419 - return {
420 - createContainer(
421 - containerInfo: C,
422 - isAsync: boolean,
423 - hydrate: boolean,
424 - ): OpaqueRoot {
425 - return createFiberRoot(containerInfo, isAsync, hydrate);
426 - },
166 + const context = getContextForSubtree(parentComponent);
167 + if (container.context === null) {
168 + container.context = context;
169 + } else {
170 + container.pendingContext = context;
171 + }
172
428 - updateContainer(
429 - element: ReactNodeList,
430 - container: OpaqueRoot,
431 - parentComponent: ?React$Component<any, any>,
432 - callback: ?Function,
433 - ): ExpirationTime {
434 - const current = container.current;
435 - const currentTime = recalculateCurrentTime();
436 - const expirationTime = computeExpirationForFiber(currentTime, current);
437 - return updateContainerAtExpirationTime(
438 - element,
439 - container,
440 - parentComponent,
441 - expirationTime,
442 - callback,
443 - );
444 - },
173 + return scheduleRootUpdate(current, element, expirationTime, callback);
174 +}
175
446 - updateContainerAtExpirationTime(
447 - element,
448 - container,
449 - parentComponent,
450 - expirationTime,
451 - callback,
452 - ) {
453 - return updateContainerAtExpirationTime(
454 - element,
455 - container,
456 - parentComponent,
457 - expirationTime,
458 - callback,
176 +function findHostInstance(component: Object): PublicInstance | null {
177 + const fiber = ReactInstanceMap.get(component);
178 + if (fiber === undefined) {
179 + if (typeof component.render === 'function') {
180 + invariant(false, 'Unable to find node on an unmounted component.');
181 + } else {
182 + invariant(
183 + false,
184 + 'Argument appears to not be a ReactComponent. Keys: %s',
185 + Object.keys(component),
186 );
460 - },
461 -
462 - flushRoot,
463 -
464 - requestWork,
465 -
466 - computeUniqueAsyncExpiration,
467 -
468 - batchedUpdates,
469 -
470 - unbatchedUpdates,
471 -
472 - deferredUpdates,
473 -
474 - syncUpdates,
187 + }
188 + }
189 + const hostFiber = findCurrentHostFiber(fiber);
190 + if (hostFiber === null) {
191 + return null;
192 + }
193 + return hostFiber.stateNode;
194 +}
195
476 - interactiveUpdates,
196 +export function createContainer(
197 + containerInfo: Container,
198 + isAsync: boolean,
199 + hydrate: boolean,
200 +): OpaqueRoot {
201 + return createFiberRoot(containerInfo, isAsync, hydrate);
202 +}
203
478 - flushInteractiveUpdates,
204 +export function updateContainer(
205 + element: ReactNodeList,
206 + container: OpaqueRoot,
207 + parentComponent: ?React$Component<any, any>,
208 + callback: ?Function,
209 +): ExpirationTime {
210 + const current = container.current;
211 + const currentTime = recalculateCurrentTime();
212 + const expirationTime = computeExpirationForFiber(currentTime, current);
213 + return updateContainerAtExpirationTime(
214 + element,
215 + container,
216 + parentComponent,
217 + expirationTime,
218 + callback,
219 + );
220 +}
221
480 - flushControlled,
222 +export {
223 + flushRoot,
224 + requestWork,
225 + computeUniqueAsyncExpiration,
226 + batchedUpdates,
227 + unbatchedUpdates,
228 + deferredUpdates,
229 + syncUpdates,
230 + interactiveUpdates,
231 + flushInteractiveUpdates,
232 + flushControlled,
233 + flushSync,
234 +};
235
482 - flushSync,
236 +export function getPublicRootInstance(
237 + container: OpaqueRoot,
238 +): React$Component<any, any> | PublicInstance | null {
239 + const containerFiber = container.current;
240 + if (!containerFiber.child) {
241 + return null;
242 + }
243 + switch (containerFiber.child.tag) {
244 + case HostComponent:
245 + return getPublicInstance(containerFiber.child.stateNode);
246 + default:
247 + return containerFiber.child.stateNode;
248 + }
249 +}
250
484 - getPublicRootInstance(
485 - container: OpaqueRoot,
486 - ): React$Component<any, any> | PI | null {
487 - const containerFiber = container.current;
488 - if (!containerFiber.child) {
489 - return null;
490 - }
491 - switch (containerFiber.child.tag) {
492 - case HostComponent:
493 - return getPublicInstance(containerFiber.child.stateNode);
494 - default:
495 - return containerFiber.child.stateNode;
496 - }
497 - },
251 +export {findHostInstance};
252
499 - findHostInstance,
253 +export function findHostInstanceWithNoPortals(
254 + fiber: Fiber,
255 +): PublicInstance | null {
256 + const hostFiber = findCurrentHostFiberWithNoPortals(fiber);
257 + if (hostFiber === null) {
258 + return null;
259 + }
260 + return hostFiber.stateNode;
261 +}
262
501 - findHostInstanceWithNoPortals(fiber: Fiber): PI | null {
502 - const hostFiber = findCurrentHostFiberWithNoPortals(fiber);
263 +export function injectIntoDevTools(devToolsConfig: DevToolsConfig): boolean {
264 + const {findFiberByHostInstance} = devToolsConfig;
265 + return ReactFiberDevToolsHook.injectInternals({
266 + ...devToolsConfig,
267 + findHostInstanceByFiber(fiber: Fiber): Instance | TextInstance | null {
268 + const hostFiber = findCurrentHostFiber(fiber);
269 if (hostFiber === null) {
270 return null;
271 }
272 return hostFiber.stateNode;
273 },
508 -
509 - injectIntoDevTools(devToolsConfig: DevToolsConfig<I, TI>): boolean {
510 - const {findFiberByHostInstance} = devToolsConfig;
511 - return ReactFiberDevToolsHook.injectInternals({
512 - ...devToolsConfig,
513 - findHostInstanceByFiber(fiber: Fiber): I | TI | null {
514 - const hostFiber = findCurrentHostFiber(fiber);
515 - if (hostFiber === null) {
516 - return null;
517 - }
518 - return hostFiber.stateNode;
519 - },
520 - findFiberByHostInstance(instance: I | TI): Fiber | null {
521 - if (!findFiberByHostInstance) {
522 - // Might not be implemented by the renderer.
523 - return null;
524 - }
525 - return findFiberByHostInstance(instance);
526 - },
527 - });
274 + findFiberByHostInstance(instance: Instance | TextInstance): Fiber | null {
275 + if (!findFiberByHostInstance) {
276 + // Might not be implemented by the renderer.
277 + return null;
278 + }
279 + return findFiberByHostInstance(instance);
280 },
529 - };
281 + });
282 }
packages/react-reconciler/src/ReactFiberScheduler.js
+1556 -1617
@@ -7,10 +7,8 @@
7 * @flow
8 */
9
10 -import type {HostConfig, Deadline} from 'react-reconciler';
10 import type {Fiber} from './ReactFiber';
11 import type {FiberRoot, Batch} from './ReactFiberRoot';
13 -import type {HydrationContext} from './ReactFiberHydrationContext';
12 import type {ExpirationTime} from './ReactFiberExpirationTime';
13
14 import ReactErrorUtils from 'shared/ReactErrorUtils';
@@ -45,19 +43,19 @@ import {
43 replayFailedUnitOfWorkWithInvokeGuardedCallback,
44 warnAboutDeprecatedLifecycles,
45 } from 'shared/ReactFeatureFlags';
48 -import {createProfilerTimer} from './ReactProfilerTimer';
46 import getComponentName from 'shared/getComponentName';
47 import invariant from 'fbjs/lib/invariant';
48 import warning from 'fbjs/lib/warning';
49
53 -import ReactFiberBeginWork from './ReactFiberBeginWork';
54 -import ReactFiberCompleteWork from './ReactFiberCompleteWork';
55 -import ReactFiberUnwindWork from './ReactFiberUnwindWork';
56 -import ReactFiberCommitWork from './ReactFiberCommitWork';
57 -import ReactFiberHostContext from './ReactFiberHostContext';
58 -import ReactFiberHydrationContext from './ReactFiberHydrationContext';
50 import ReactFiberInstrumentation from './ReactFiberInstrumentation';
51 import ReactDebugCurrentFiber from './ReactDebugCurrentFiber';
52 +import {
53 + now,
54 + scheduleDeferredCallback,
55 + cancelDeferredCallback,
56 + prepareForCommit,
57 + resetAfterCommit,
58 +} from './ReactFiberHostConfig';
59 import {
60 markPendingPriorityLevel,
61 markCommittedPriorityLevels,
@@ -95,11 +93,51 @@ import {
93 computeExpirationBucket,
94 } from './ReactFiberExpirationTime';
95 import {AsyncMode, ProfileMode} from './ReactTypeOfMode';
98 -import ReactFiberLegacyContext from './ReactFiberContext';
99 -import ReactFiberNewContext from './ReactFiberNewContext';
96 import {enqueueUpdate, resetCurrentlyProcessingQueue} from './ReactUpdateQueue';
97 import {createCapturedValue} from './ReactCapturedValue';
102 -import ReactFiberStack from './ReactFiberStack';
98 +import {
99 + popTopLevelContextObject as popTopLevelLegacyContextObject,
100 + popContextProvider as popLegacyContextProvider,
101 +} from './ReactFiberContext';
102 +import {popProvider} from './ReactFiberNewContext';
103 +import {popHostContext, popHostContainer} from './ReactFiberHostContext';
104 +import {
105 + checkActualRenderTimeStackEmpty,
106 + pauseActualRenderTimerIfRunning,
107 + recordElapsedBaseRenderTimeIfRunning,
108 + resetActualRenderTimer,
109 + resumeActualRenderTimerIfPaused,
110 + startBaseRenderTimer,
111 + stopBaseRenderTimerIfRunning,
112 +} from './ReactProfilerTimer';
113 +import {
114 + checkThatStackIsEmpty,
115 + resetStackAfterFatalErrorInDev,
116 +} from './ReactFiberStack';
117 +import {beginWork} from './ReactFiberBeginWork';
118 +import {completeWork} from './ReactFiberCompleteWork';
119 +import {
120 + throwException,
121 + unwindWork,
122 + unwindInterruptedWork,
123 + createRootErrorUpdate,
124 + createClassErrorUpdate,
125 +} from './ReactFiberUnwindWork';
126 +import {
127 + commitBeforeMutationLifeCycles,
128 + commitResetTextContent,
129 + commitPlacement,
130 + commitDeletion,
131 + commitWork,
132 + commitLifeCycles,
133 + commitAttachRef,
134 + commitDetachRef,
135 +} from './ReactFiberCommitWork';
136 +
137 +export type Deadline = {
138 + timeRemaining: () => number,
139 + didTimeout: boolean,
140 +};
141
142 export type Thenable = {
143 then(resolve: () => mixed, reject?: () => mixed): mixed,
@@ -168,1057 +206,953 @@ if (__DEV__) {
206 };
207 }
208
171 -export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
172 - config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
173 -) {
174 - const {
175 - now,
176 - scheduleDeferredCallback,
177 - cancelDeferredCallback,
178 - prepareForCommit,
179 - resetAfterCommit,
180 - } = config;
181 - const stack = ReactFiberStack();
182 - const hostContext = ReactFiberHostContext(config, stack);
183 - const legacyContext = ReactFiberLegacyContext(stack);
184 - const newContext = ReactFiberNewContext(stack, config.isPrimaryRenderer);
185 - const profilerTimer = createProfilerTimer(now);
186 - const {popHostContext, popHostContainer} = hostContext;
187 - const {
188 - popTopLevelContextObject: popTopLevelLegacyContextObject,
189 - popContextProvider: popLegacyContextProvider,
190 - } = legacyContext;
191 - const {popProvider} = newContext;
192 - const hydrationContext: HydrationContext<C, CX> = ReactFiberHydrationContext(
193 - config,
194 - );
195 - const {beginWork} = ReactFiberBeginWork(
196 - config,
197 - hostContext,
198 - legacyContext,
199 - newContext,
200 - hydrationContext,
201 - scheduleWork,
202 - computeExpirationForFiber,
203 - profilerTimer,
204 - recalculateCurrentTime,
205 - );
206 - const {completeWork} = ReactFiberCompleteWork(
207 - config,
208 - hostContext,
209 - legacyContext,
210 - newContext,
211 - hydrationContext,
212 - profilerTimer,
213 - );
214 - const {
215 - throwException,
216 - unwindWork,
217 - unwindInterruptedWork,
218 - createRootErrorUpdate,
219 - createClassErrorUpdate,
220 - } = ReactFiberUnwindWork(
221 - config,
222 - hostContext,
223 - legacyContext,
224 - newContext,
225 - scheduleWork,
226 - computeExpirationForFiber,
227 - recalculateCurrentTime,
228 - markLegacyErrorBoundaryAsFailed,
229 - isAlreadyFailedLegacyErrorBoundary,
230 - onUncaughtError,
231 - profilerTimer,
232 - suspendRoot,
233 - retrySuspendedRoot,
234 - );
235 - const {
236 - commitBeforeMutationLifeCycles,
237 - commitResetTextContent,
238 - commitPlacement,
239 - commitDeletion,
240 - commitWork,
241 - commitLifeCycles,
242 - commitAttachRef,
243 - commitDetachRef,
244 - } = ReactFiberCommitWork(
245 - config,
246 - onCommitPhaseError,
247 - scheduleWork,
248 - computeExpirationForFiber,
249 - markLegacyErrorBoundaryAsFailed,
250 - recalculateCurrentTime,
251 - );
252 -
253 - const {
254 - checkActualRenderTimeStackEmpty,
255 - pauseActualRenderTimerIfRunning,
256 - recordElapsedBaseRenderTimeIfRunning,
257 - resetActualRenderTimer,
258 - resumeActualRenderTimerIfPaused,
259 - startBaseRenderTimer,
260 - stopBaseRenderTimerIfRunning,
261 - } = profilerTimer;
262 -
263 - // Represents the current time in ms.
264 - const originalStartTimeMs = now();
265 - let mostRecentCurrentTime: ExpirationTime = msToExpirationTime(0);
266 - let mostRecentCurrentTimeMs: number = originalStartTimeMs;
267 -
268 - // Used to ensure computeUniqueAsyncExpiration is monotonically increases.
269 - let lastUniqueAsyncExpiration: number = 0;
270 -
271 - // Represents the expiration time that incoming updates should use. (If this
272 - // is NoWork, use the default strategy: async updates in async mode, sync
273 - // updates in sync mode.)
274 - let expirationContext: ExpirationTime = NoWork;
275 -
276 - let isWorking: boolean = false;
277 -
278 - // The next work in progress fiber that we're currently working on.
279 - let nextUnitOfWork: Fiber | null = null;
280 - let nextRoot: FiberRoot | null = null;
281 - // The time at which we're currently rendering work.
282 - let nextRenderExpirationTime: ExpirationTime = NoWork;
283 - let nextLatestTimeoutMs: number = -1;
284 - let nextRenderIsExpired: boolean = false;
285 -
286 - // The next fiber with an effect that we're currently committing.
287 - let nextEffect: Fiber | null = null;
288 -
289 - let isCommitting: boolean = false;
290 -
291 - let isRootReadyForCommit: boolean = false;
292 -
293 - let legacyErrorBoundariesThatAlreadyFailed: Set<mixed> | null = null;
209 +// Represents the current time in ms.
210 +const originalStartTimeMs = now();
211 +let mostRecentCurrentTime: ExpirationTime = msToExpirationTime(0);
212 +let mostRecentCurrentTimeMs: number = originalStartTimeMs;
213 +
214 +// Used to ensure computeUniqueAsyncExpiration is monotonically increases.
215 +let lastUniqueAsyncExpiration: number = 0;
216 +
217 +// Represents the expiration time that incoming updates should use. (If this
218 +// is NoWork, use the default strategy: async updates in async mode, sync
219 +// updates in sync mode.)
220 +let expirationContext: ExpirationTime = NoWork;
221 +
222 +let isWorking: boolean = false;
223 +
224 +// The next work in progress fiber that we're currently working on.
225 +let nextUnitOfWork: Fiber | null = null;
226 +let nextRoot: FiberRoot | null = null;
227 +// The time at which we're currently rendering work.
228 +let nextRenderExpirationTime: ExpirationTime = NoWork;
229 +let nextLatestTimeoutMs: number = -1;
230 +let nextRenderIsExpired: boolean = false;
231 +
232 +// The next fiber with an effect that we're currently committing.
233 +let nextEffect: Fiber | null = null;
234 +
235 +let isCommitting: boolean = false;
236 +
237 +let isRootReadyForCommit: boolean = false;
238 +
239 +let legacyErrorBoundariesThatAlreadyFailed: Set<mixed> | null = null;
240 +
241 +// Used for performance tracking.
242 +let interruptedBy: Fiber | null = null;
243 +
244 +let stashedWorkInProgressProperties;
245 +let replayUnitOfWork;
246 +let isReplayingFailedUnitOfWork;
247 +let originalReplayError;
248 +let rethrowOriginalError;
249 +if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
250 + stashedWorkInProgressProperties = null;
251 + isReplayingFailedUnitOfWork = false;
252 + originalReplayError = null;
253 + replayUnitOfWork = (
254 + failedUnitOfWork: Fiber,
255 + thrownValue: mixed,
256 + isAsync: boolean,
257 + ) => {
258 + if (
259 + thrownValue !== null &&
260 + typeof thrownValue === 'object' &&
261 + typeof thrownValue.then === 'function'
262 + ) {
263 + // Don't replay promises. Treat everything else like an error.
264 + // TODO: Need to figure out a different strategy if/when we add
265 + // support for catching other types.
266 + return;
267 + }
268
295 - // Used for performance tracking.
296 - let interruptedBy: Fiber | null = null;
269 + // Restore the original state of the work-in-progress
270 + if (stashedWorkInProgressProperties === null) {
271 + // This should never happen. Don't throw because this code is DEV-only.
272 + warning(
273 + false,
274 + 'Could not replay rendering after an error. This is likely a bug in React. ' +
275 + 'Please file an issue.',
276 + );
277 + return;
278 + }
279 + assignFiberPropertiesInDEV(
280 + failedUnitOfWork,
281 + stashedWorkInProgressProperties,
282 + );
283
298 - let stashedWorkInProgressProperties;
299 - let replayUnitOfWork;
300 - let isReplayingFailedUnitOfWork;
301 - let originalReplayError;
302 - let rethrowOriginalError;
303 - if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
304 - stashedWorkInProgressProperties = null;
284 + switch (failedUnitOfWork.tag) {
285 + case HostRoot:
286 + popHostContainer(failedUnitOfWork);
287 + popTopLevelLegacyContextObject(failedUnitOfWork);
288 + break;
289 + case HostComponent:
290 + popHostContext(failedUnitOfWork);
291 + break;
292 + case ClassComponent:
293 + popLegacyContextProvider(failedUnitOfWork);
294 + break;
295 + case HostPortal:
296 + popHostContainer(failedUnitOfWork);
297 + break;
298 + case ContextProvider:
299 + popProvider(failedUnitOfWork);
300 + break;
301 + }
302 + // Replay the begin phase.
303 + isReplayingFailedUnitOfWork = true;
304 + originalReplayError = thrownValue;
305 + invokeGuardedCallback(null, workLoop, null, isAsync);
306 isReplayingFailedUnitOfWork = false;
307 originalReplayError = null;
307 - replayUnitOfWork = (
308 - failedUnitOfWork: Fiber,
309 - thrownValue: mixed,
310 - isAsync: boolean,
311 - ) => {
312 - if (
313 - thrownValue !== null &&
314 - typeof thrownValue === 'object' &&
315 - typeof thrownValue.then === 'function'
316 - ) {
317 - // Don't replay promises. Treat everything else like an error.
318 - // TODO: Need to figure out a different strategy if/when we add
319 - // support for catching other types.
320 - return;
321 - }
322 -
323 - // Restore the original state of the work-in-progress
324 - if (stashedWorkInProgressProperties === null) {
325 - // This should never happen. Don't throw because this code is DEV-only.
326 - warning(
327 - false,
328 - 'Could not replay rendering after an error. This is likely a bug in React. ' +
329 - 'Please file an issue.',
330 - );
331 - return;
332 - }
333 - assignFiberPropertiesInDEV(
334 - failedUnitOfWork,
335 - stashedWorkInProgressProperties,
336 - );
337 -
338 - switch (failedUnitOfWork.tag) {
339 - case HostRoot:
340 - popHostContainer(failedUnitOfWork);
341 - popTopLevelLegacyContextObject(failedUnitOfWork);
342 - break;
343 - case HostComponent:
344 - popHostContext(failedUnitOfWork);
345 - break;
346 - case ClassComponent:
347 - popLegacyContextProvider(failedUnitOfWork);
348 - break;
349 - case HostPortal:
350 - popHostContainer(failedUnitOfWork);
351 - break;
352 - case ContextProvider:
353 - popProvider(failedUnitOfWork);
354 - break;
355 - }
356 - // Replay the begin phase.
357 - isReplayingFailedUnitOfWork = true;
358 - originalReplayError = thrownValue;
359 - invokeGuardedCallback(null, workLoop, null, isAsync);
360 - isReplayingFailedUnitOfWork = false;
361 - originalReplayError = null;
362 - if (hasCaughtError()) {
363 - clearCaughtError();
308 + if (hasCaughtError()) {
309 + clearCaughtError();
310
365 - if (enableProfilerTimer) {
366 - // Stop "base" render timer again (after the re-thrown error).
367 - stopBaseRenderTimerIfRunning();
368 - }
369 - } else {
370 - // If the begin phase did not fail the second time, set this pointer
371 - // back to the original value.
372 - nextUnitOfWork = failedUnitOfWork;
373 - }
374 - };
375 - rethrowOriginalError = () => {
376 - throw originalReplayError;
377 - };
378 - }
379 -
380 - function resetStack() {
381 - if (nextUnitOfWork !== null) {
382 - let interruptedWork = nextUnitOfWork.return;
383 - while (interruptedWork !== null) {
384 - unwindInterruptedWork(interruptedWork);
385 - interruptedWork = interruptedWork.return;
311 + if (enableProfilerTimer) {
312 + // Stop "base" render timer again (after the re-thrown error).
313 + stopBaseRenderTimerIfRunning();
314 }
315 + } else {
316 + // If the begin phase did not fail the second time, set this pointer
317 + // back to the original value.
318 + nextUnitOfWork = failedUnitOfWork;
319 }
320 + };
321 + rethrowOriginalError = () => {
322 + throw originalReplayError;
323 + };
324 +}
325
389 - if (__DEV__) {
390 - ReactStrictModeWarnings.discardPendingWarnings();
391 - stack.checkThatStackIsEmpty();
326 +function resetStack() {
327 + if (nextUnitOfWork !== null) {
328 + let interruptedWork = nextUnitOfWork.return;
329 + while (interruptedWork !== null) {
330 + unwindInterruptedWork(interruptedWork);
331 + interruptedWork = interruptedWork.return;
332 }
333 + }
334
394 - nextRoot = null;
395 - nextRenderExpirationTime = NoWork;
396 - nextLatestTimeoutMs = -1;
397 - nextRenderIsExpired = false;
398 - nextUnitOfWork = null;
399 -
400 - isRootReadyForCommit = false;
335 + if (__DEV__) {
336 + ReactStrictModeWarnings.discardPendingWarnings();
337 + checkThatStackIsEmpty();
338 }
339
403 - function commitAllHostEffects() {
404 - while (nextEffect !== null) {
405 - if (__DEV__) {
406 - ReactDebugCurrentFiber.setCurrentFiber(nextEffect);
407 - }
408 - recordEffect();
340 + nextRoot = null;
341 + nextRenderExpirationTime = NoWork;
342 + nextLatestTimeoutMs = -1;
343 + nextRenderIsExpired = false;
344 + nextUnitOfWork = null;
345
410 - const effectTag = nextEffect.effectTag;
346 + isRootReadyForCommit = false;
347 +}
348
412 - if (effectTag & ContentReset) {
413 - commitResetTextContent(nextEffect);
414 - }
349 +function commitAllHostEffects() {
350 + while (nextEffect !== null) {
351 + if (__DEV__) {
352 + ReactDebugCurrentFiber.setCurrentFiber(nextEffect);
353 + }
354 + recordEffect();
355
416 - if (effectTag & Ref) {
417 - const current = nextEffect.alternate;
418 - if (current !== null) {
419 - commitDetachRef(current);
420 - }
421 - }
356 + const effectTag = nextEffect.effectTag;
357
423 - // The following switch statement is only concerned about placement,
424 - // updates, and deletions. To avoid needing to add a case for every
425 - // possible bitmap value, we remove the secondary effects from the
426 - // effect tag and switch on that value.
427 - let primaryEffectTag = effectTag & (Placement | Update | Deletion);
428 - switch (primaryEffectTag) {
429 - case Placement: {
430 - commitPlacement(nextEffect);
431 - // Clear the "placement" from effect tag so that we know that this is inserted, before
432 - // any life-cycles like componentDidMount gets called.
433 - // TODO: findDOMNode doesn't rely on this any more but isMounted
434 - // does and isMounted is deprecated anyway so we should be able
435 - // to kill this.
436 - nextEffect.effectTag &= ~Placement;
437 - break;
438 - }
439 - case PlacementAndUpdate: {
440 - // Placement
441 - commitPlacement(nextEffect);
442 - // Clear the "placement" from effect tag so that we know that this is inserted, before
443 - // any life-cycles like componentDidMount gets called.
444 - nextEffect.effectTag &= ~Placement;
445 -
446 - // Update
447 - const current = nextEffect.alternate;
448 - commitWork(current, nextEffect);
449 - break;
450 - }
451 - case Update: {
452 - const current = nextEffect.alternate;
453 - commitWork(current, nextEffect);
454 - break;
455 - }
456 - case Deletion: {
457 - commitDeletion(nextEffect);
458 - break;
459 - }
460 - }
461 - nextEffect = nextEffect.nextEffect;
358 + if (effectTag & ContentReset) {
359 + commitResetTextContent(nextEffect);
360 }
361
464 - if (__DEV__) {
465 - ReactDebugCurrentFiber.resetCurrentFiber();
362 + if (effectTag & Ref) {
363 + const current = nextEffect.alternate;
364 + if (current !== null) {
365 + commitDetachRef(current);
366 + }
367 }
467 - }
368
469 - function commitBeforeMutationLifecycles() {
470 - while (nextEffect !== null) {
471 - const effectTag = nextEffect.effectTag;
472 -
473 - if (effectTag & Snapshot) {
474 - recordEffect();
369 + // The following switch statement is only concerned about placement,
370 + // updates, and deletions. To avoid needing to add a case for every
371 + // possible bitmap value, we remove the secondary effects from the
372 + // effect tag and switch on that value.
373 + let primaryEffectTag = effectTag & (Placement | Update | Deletion);
374 + switch (primaryEffectTag) {
375 + case Placement: {
376 + commitPlacement(nextEffect);
377 + // Clear the "placement" from effect tag so that we know that this is inserted, before
378 + // any life-cycles like componentDidMount gets called.
379 + // TODO: findDOMNode doesn't rely on this any more but isMounted
380 + // does and isMounted is deprecated anyway so we should be able
381 + // to kill this.
382 + nextEffect.effectTag &= ~Placement;
383 + break;
384 + }
385 + case PlacementAndUpdate: {
386 + // Placement
387 + commitPlacement(nextEffect);
388 + // Clear the "placement" from effect tag so that we know that this is inserted, before
389 + // any life-cycles like componentDidMount gets called.
390 + nextEffect.effectTag &= ~Placement;
391 +
392 + // Update
393 const current = nextEffect.alternate;
476 - commitBeforeMutationLifeCycles(current, nextEffect);
394 + commitWork(current, nextEffect);
395 + break;
396 + }
397 + case Update: {
398 + const current = nextEffect.alternate;
399 + commitWork(current, nextEffect);
400 + break;
401 + }
402 + case Deletion: {
403 + commitDeletion(nextEffect);
404 + break;
405 }
478 -
479 - // Don't cleanup effects yet;
480 - // This will be done by commitAllLifeCycles()
481 - nextEffect = nextEffect.nextEffect;
406 }
407 + nextEffect = nextEffect.nextEffect;
408 }
409
485 - function commitAllLifeCycles(
486 - finishedRoot: FiberRoot,
487 - currentTime: ExpirationTime,
488 - committedExpirationTime: ExpirationTime,
489 - ) {
490 - if (__DEV__) {
491 - ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings();
410 + if (__DEV__) {
411 + ReactDebugCurrentFiber.resetCurrentFiber();
412 + }
413 +}
414
493 - if (warnAboutDeprecatedLifecycles) {
494 - ReactStrictModeWarnings.flushPendingDeprecationWarnings();
495 - }
415 +function commitBeforeMutationLifecycles() {
416 + while (nextEffect !== null) {
417 + const effectTag = nextEffect.effectTag;
418 +
419 + if (effectTag & Snapshot) {
420 + recordEffect();
421 + const current = nextEffect.alternate;
422 + commitBeforeMutationLifeCycles(current, nextEffect);
423 }
497 - while (nextEffect !== null) {
498 - const effectTag = nextEffect.effectTag;
424
500 - if (effectTag & (Update | Callback)) {
501 - recordEffect();
502 - const current = nextEffect.alternate;
503 - commitLifeCycles(
504 - finishedRoot,
505 - current,
506 - nextEffect,
507 - currentTime,
508 - committedExpirationTime,
509 - );
510 - }
425 + // Don't cleanup effects yet;
426 + // This will be done by commitAllLifeCycles()
427 + nextEffect = nextEffect.nextEffect;
428 + }
429 +}
430
512 - if (effectTag & Ref) {
513 - recordEffect();
514 - commitAttachRef(nextEffect);
515 - }
431 +function commitAllLifeCycles(
432 + finishedRoot: FiberRoot,
433 + currentTime: ExpirationTime,
434 + committedExpirationTime: ExpirationTime,
435 +) {
436 + if (__DEV__) {
437 + ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings();
438
517 - const next = nextEffect.nextEffect;
518 - // Ensure that we clean these up so that we don't accidentally keep them.
519 - // I'm not actually sure this matters because we can't reset firstEffect
520 - // and lastEffect since they're on every node, not just the effectful
521 - // ones. So we have to clean everything as we reuse nodes anyway.
522 - nextEffect.nextEffect = null;
523 - // Ensure that we reset the effectTag here so that we can rely on effect
524 - // tags to reason about the current life-cycle.
525 - nextEffect = next;
439 + if (warnAboutDeprecatedLifecycles) {
440 + ReactStrictModeWarnings.flushPendingDeprecationWarnings();
441 }
442 }
443 + while (nextEffect !== null) {
444 + const effectTag = nextEffect.effectTag;
445
529 - function isAlreadyFailedLegacyErrorBoundary(instance: mixed): boolean {
530 - return (
531 - legacyErrorBoundariesThatAlreadyFailed !== null &&
532 - legacyErrorBoundariesThatAlreadyFailed.has(instance)
533 - );
534 - }
535 -
536 - function markLegacyErrorBoundaryAsFailed(instance: mixed) {
537 - if (legacyErrorBoundariesThatAlreadyFailed === null) {
538 - legacyErrorBoundariesThatAlreadyFailed = new Set([instance]);
539 - } else {
540 - legacyErrorBoundariesThatAlreadyFailed.add(instance);
446 + if (effectTag & (Update | Callback)) {
447 + recordEffect();
448 + const current = nextEffect.alternate;
449 + commitLifeCycles(
450 + finishedRoot,
451 + current,
452 + nextEffect,
453 + currentTime,
454 + committedExpirationTime,
455 + );
456 }
457 +
458 + if (effectTag & Ref) {
459 + recordEffect();
460 + commitAttachRef(nextEffect);
461 + }
462 +
463 + const next = nextEffect.nextEffect;
464 + // Ensure that we clean these up so that we don't accidentally keep them.
465 + // I'm not actually sure this matters because we can't reset firstEffect
466 + // and lastEffect since they're on every node, not just the effectful
467 + // ones. So we have to clean everything as we reuse nodes anyway.
468 + nextEffect.nextEffect = null;
469 + // Ensure that we reset the effectTag here so that we can rely on effect
470 + // tags to reason about the current life-cycle.
471 + nextEffect = next;
472 }
473 +}
474 +
475 +function isAlreadyFailedLegacyErrorBoundary(instance: mixed): boolean {
476 + return (
477 + legacyErrorBoundariesThatAlreadyFailed !== null &&
478 + legacyErrorBoundariesThatAlreadyFailed.has(instance)
479 + );
480 +}
481
544 - function commitRoot(finishedWork: Fiber): ExpirationTime {
545 - isWorking = true;
546 - isCommitting = true;
547 - startCommitTimer();
482 +function markLegacyErrorBoundaryAsFailed(instance: mixed) {
483 + if (legacyErrorBoundariesThatAlreadyFailed === null) {
484 + legacyErrorBoundariesThatAlreadyFailed = new Set([instance]);
485 + } else {
486 + legacyErrorBoundariesThatAlreadyFailed.add(instance);
487 + }
488 +}
489
549 - const root: FiberRoot = finishedWork.stateNode;
550 - invariant(
551 - root.current !== finishedWork,
552 - 'Cannot commit the same tree as before. This is probably a bug ' +
553 - 'related to the return field. This error is likely caused by a bug ' +
554 - 'in React. Please file an issue.',
555 - );
556 - const committedExpirationTime = root.pendingCommitExpirationTime;
557 - invariant(
558 - committedExpirationTime !== NoWork,
559 - 'Cannot commit an incomplete root. This error is likely caused by a ' +
560 - 'bug in React. Please file an issue.',
561 - );
562 - root.pendingCommitExpirationTime = NoWork;
490 +function commitRoot(finishedWork: Fiber): ExpirationTime {
491 + isWorking = true;
492 + isCommitting = true;
493 + startCommitTimer();
494 +
495 + const root: FiberRoot = finishedWork.stateNode;
496 + invariant(
497 + root.current !== finishedWork,
498 + 'Cannot commit the same tree as before. This is probably a bug ' +
499 + 'related to the return field. This error is likely caused by a bug ' +
500 + 'in React. Please file an issue.',
501 + );
502 + const committedExpirationTime = root.pendingCommitExpirationTime;
503 + invariant(
504 + committedExpirationTime !== NoWork,
505 + 'Cannot commit an incomplete root. This error is likely caused by a ' +
506 + 'bug in React. Please file an issue.',
507 + );
508 + root.pendingCommitExpirationTime = NoWork;
509
564 - const currentTime = recalculateCurrentTime();
510 + const currentTime = recalculateCurrentTime();
511
566 - // Reset this to null before calling lifecycles
567 - ReactCurrentOwner.current = null;
512 + // Reset this to null before calling lifecycles
513 + ReactCurrentOwner.current = null;
514
569 - let firstEffect;
570 - if (finishedWork.effectTag > PerformedWork) {
571 - // A fiber's effect list consists only of its children, not itself. So if
572 - // the root has an effect, we need to add it to the end of the list. The
573 - // resulting list is the set that would belong to the root's parent, if
574 - // it had one; that is, all the effects in the tree including the root.
575 - if (finishedWork.lastEffect !== null) {
576 - finishedWork.lastEffect.nextEffect = finishedWork;
577 - firstEffect = finishedWork.firstEffect;
578 - } else {
579 - firstEffect = finishedWork;
580 - }
581 - } else {
582 - // There is no effect on the root.
515 + let firstEffect;
516 + if (finishedWork.effectTag > PerformedWork) {
517 + // A fiber's effect list consists only of its children, not itself. So if
518 + // the root has an effect, we need to add it to the end of the list. The
519 + // resulting list is the set that would belong to the root's parent, if
520 + // it had one; that is, all the effects in the tree including the root.
521 + if (finishedWork.lastEffect !== null) {
522 + finishedWork.lastEffect.nextEffect = finishedWork;
523 firstEffect = finishedWork.firstEffect;
524 + } else {
525 + firstEffect = finishedWork;
526 }
527 + } else {
528 + // There is no effect on the root.
529 + firstEffect = finishedWork.firstEffect;
530 + }
531
586 - prepareForCommit(root.containerInfo);
532 + prepareForCommit(root.containerInfo);
533
588 - // Invoke instances of getSnapshotBeforeUpdate before mutation.
589 - nextEffect = firstEffect;
590 - startCommitSnapshotEffectsTimer();
591 - while (nextEffect !== null) {
592 - let didError = false;
593 - let error;
594 - if (__DEV__) {
595 - invokeGuardedCallback(null, commitBeforeMutationLifecycles, null);
596 - if (hasCaughtError()) {
597 - didError = true;
598 - error = clearCaughtError();
599 - }
600 - } else {
601 - try {
602 - commitBeforeMutationLifecycles();
603 - } catch (e) {
604 - didError = true;
605 - error = e;
606 - }
534 + // Invoke instances of getSnapshotBeforeUpdate before mutation.
535 + nextEffect = firstEffect;
536 + startCommitSnapshotEffectsTimer();
537 + while (nextEffect !== null) {
538 + let didError = false;
539 + let error;
540 + if (__DEV__) {
541 + invokeGuardedCallback(null, commitBeforeMutationLifecycles, null);
542 + if (hasCaughtError()) {
543 + didError = true;
544 + error = clearCaughtError();
545 }
608 - if (didError) {
609 - invariant(
610 - nextEffect !== null,
611 - 'Should have next effect. This error is likely caused by a bug ' +
612 - 'in React. Please file an issue.',
613 - );
614 - onCommitPhaseError(nextEffect, error);
615 - // Clean-up
616 - if (nextEffect !== null) {
617 - nextEffect = nextEffect.nextEffect;
618 - }
546 + } else {
547 + try {
548 + commitBeforeMutationLifecycles();
549 + } catch (e) {
550 + didError = true;
551 + error = e;
552 }
553 }
621 - stopCommitSnapshotEffectsTimer();
622 -
623 - // Commit all the side-effects within a tree. We'll do this in two passes.
624 - // The first pass performs all the host insertions, updates, deletions and
625 - // ref unmounts.
626 - nextEffect = firstEffect;
627 - startCommitHostEffectsTimer();
628 - while (nextEffect !== null) {
629 - let didError = false;
630 - let error;
631 - if (__DEV__) {
632 - invokeGuardedCallback(null, commitAllHostEffects, null);
633 - if (hasCaughtError()) {
634 - didError = true;
635 - error = clearCaughtError();
636 - }
637 - } else {
638 - try {
639 - commitAllHostEffects();
640 - } catch (e) {
641 - didError = true;
642 - error = e;
643 - }
644 - }
645 - if (didError) {
646 - invariant(
647 - nextEffect !== null,
648 - 'Should have next effect. This error is likely caused by a bug ' +
649 - 'in React. Please file an issue.',
650 - );
651 - onCommitPhaseError(nextEffect, error);
652 - // Clean-up
653 - if (nextEffect !== null) {
654 - nextEffect = nextEffect.nextEffect;
655 - }
554 + if (didError) {
555 + invariant(
556 + nextEffect !== null,
557 + 'Should have next effect. This error is likely caused by a bug ' +
558 + 'in React. Please file an issue.',
559 + );
560 + captureCommitPhaseError(nextEffect, error);
561 + // Clean-up
562 + if (nextEffect !== null) {
563 + nextEffect = nextEffect.nextEffect;
564 }
565 }
658 - stopCommitHostEffectsTimer();
659 -
660 - resetAfterCommit(root.containerInfo);
661 -
662 - // The work-in-progress tree is now the current tree. This must come after
663 - // the first pass of the commit phase, so that the previous tree is still
664 - // current during componentWillUnmount, but before the second pass, so that
665 - // the finished work is current during componentDidMount/Update.
666 - root.current = finishedWork;
667 -
668 - // In the second pass we'll perform all life-cycles and ref callbacks.
669 - // Life-cycles happen as a separate pass so that all placements, updates,
670 - // and deletions in the entire tree have already been invoked.
671 - // This pass also triggers any renderer-specific initial effects.
672 - nextEffect = firstEffect;
673 - startCommitLifeCyclesTimer();
674 - while (nextEffect !== null) {
675 - let didError = false;
676 - let error;
677 - if (__DEV__) {
678 - invokeGuardedCallback(
679 - null,
680 - commitAllLifeCycles,
681 - null,
682 - root,
683 - currentTime,
684 - committedExpirationTime,
685 - );
686 - if (hasCaughtError()) {
687 - didError = true;
688 - error = clearCaughtError();
689 - }
690 - } else {
691 - try {
692 - commitAllLifeCycles(root, currentTime, committedExpirationTime);
693 - } catch (e) {
694 - didError = true;
695 - error = e;
696 - }
566 + }
567 + stopCommitSnapshotEffectsTimer();
568 +
569 + // Commit all the side-effects within a tree. We'll do this in two passes.
570 + // The first pass performs all the host insertions, updates, deletions and
571 + // ref unmounts.
572 + nextEffect = firstEffect;
573 + startCommitHostEffectsTimer();
574 + while (nextEffect !== null) {
575 + let didError = false;
576 + let error;
577 + if (__DEV__) {
578 + invokeGuardedCallback(null, commitAllHostEffects, null);
579 + if (hasCaughtError()) {
580 + didError = true;
581 + error = clearCaughtError();
582 }
698 - if (didError) {
699 - invariant(
700 - nextEffect !== null,
701 - 'Should have next effect. This error is likely caused by a bug ' +
702 - 'in React. Please file an issue.',
703 - );
704 - onCommitPhaseError(nextEffect, error);
705 - if (nextEffect !== null) {
706 - nextEffect = nextEffect.nextEffect;
707 - }
583 + } else {
584 + try {
585 + commitAllHostEffects();
586 + } catch (e) {
587 + didError = true;
588 + error = e;
589 }
590 }
710 -
711 - if (enableProfilerTimer) {
712 - if (__DEV__) {
713 - checkActualRenderTimeStackEmpty();
591 + if (didError) {
592 + invariant(
593 + nextEffect !== null,
594 + 'Should have next effect. This error is likely caused by a bug ' +
595 + 'in React. Please file an issue.',
596 + );
597 + captureCommitPhaseError(nextEffect, error);
598 + // Clean-up
599 + if (nextEffect !== null) {
600 + nextEffect = nextEffect.nextEffect;
601 }
715 - resetActualRenderTimer();
602 }
717 -
718 - isCommitting = false;
719 - isWorking = false;
720 - stopCommitLifeCyclesTimer();
721 - stopCommitTimer();
722 - if (typeof onCommitRoot === 'function') {
723 - onCommitRoot(finishedWork.stateNode);
603 + }
604 + stopCommitHostEffectsTimer();
605 +
606 + resetAfterCommit(root.containerInfo);
607 +
608 + // The work-in-progress tree is now the current tree. This must come after
609 + // the first pass of the commit phase, so that the previous tree is still
610 + // current during componentWillUnmount, but before the second pass, so that
611 + // the finished work is current during componentDidMount/Update.
612 + root.current = finishedWork;
613 +
614 + // In the second pass we'll perform all life-cycles and ref callbacks.
615 + // Life-cycles happen as a separate pass so that all placements, updates,
616 + // and deletions in the entire tree have already been invoked.
617 + // This pass also triggers any renderer-specific initial effects.
618 + nextEffect = firstEffect;
619 + startCommitLifeCyclesTimer();
620 + while (nextEffect !== null) {
621 + let didError = false;
622 + let error;
623 + if (__DEV__) {
624 + invokeGuardedCallback(
625 + null,
626 + commitAllLifeCycles,
627 + null,
628 + root,
629 + currentTime,
630 + committedExpirationTime,
631 + );
632 + if (hasCaughtError()) {
633 + didError = true;
634 + error = clearCaughtError();
635 + }
636 + } else {
637 + try {
638 + commitAllLifeCycles(root, currentTime, committedExpirationTime);
639 + } catch (e) {
640 + didError = true;
641 + error = e;
642 + }
643 }
725 - if (__DEV__ && ReactFiberInstrumentation.debugTool) {
726 - ReactFiberInstrumentation.debugTool.onCommitWork(finishedWork);
644 + if (didError) {
645 + invariant(
646 + nextEffect !== null,
647 + 'Should have next effect. This error is likely caused by a bug ' +
648 + 'in React. Please file an issue.',
649 + );
650 + captureCommitPhaseError(nextEffect, error);
651 + if (nextEffect !== null) {
652 + nextEffect = nextEffect.nextEffect;
653 + }
654 }
655 + }
656
729 - markCommittedPriorityLevels(root, currentTime, root.current.expirationTime);
730 - const remainingTime = findNextPendingPriorityLevel(root);
731 - if (remainingTime === NoWork) {
732 - // If there's no remaining work, we can clear the set of already failed
733 - // error boundaries.
734 - legacyErrorBoundariesThatAlreadyFailed = null;
657 + if (enableProfilerTimer) {
658 + if (__DEV__) {
659 + checkActualRenderTimeStackEmpty();
660 }
736 - return remainingTime;
661 + resetActualRenderTimer();
662 }
663
739 - function resetExpirationTime(
740 - workInProgress: Fiber,
741 - renderTime: ExpirationTime,
742 - ) {
743 - if (renderTime !== Never && workInProgress.expirationTime === Never) {
744 - // The children of this component are hidden. Don't bubble their
745 - // expiration times.
746 - return;
747 - }
664 + isCommitting = false;
665 + isWorking = false;
666 + stopCommitLifeCyclesTimer();
667 + stopCommitTimer();
668 + if (typeof onCommitRoot === 'function') {
669 + onCommitRoot(finishedWork.stateNode);
670 + }
671 + if (__DEV__ && ReactFiberInstrumentation.debugTool) {
672 + ReactFiberInstrumentation.debugTool.onCommitWork(finishedWork);
673 + }
674
749 - // Check for pending updates.
750 - let newExpirationTime = NoWork;
751 - switch (workInProgress.tag) {
752 - case HostRoot:
753 - case ClassComponent: {
754 - const updateQueue = workInProgress.updateQueue;
755 - if (updateQueue !== null) {
756 - newExpirationTime = updateQueue.expirationTime;
757 - }
675 + markCommittedPriorityLevels(root, currentTime, root.current.expirationTime);
676 + const remainingTime = findNextPendingPriorityLevel(root);
677 + if (remainingTime === NoWork) {
678 + // If there's no remaining work, we can clear the set of already failed
679 + // error boundaries.
680 + legacyErrorBoundariesThatAlreadyFailed = null;
681 + }
682 + return remainingTime;
683 +}
684 +
685 +function resetExpirationTime(
686 + workInProgress: Fiber,
687 + renderTime: ExpirationTime,
688 +) {
689 + if (renderTime !== Never && workInProgress.expirationTime === Never) {
690 + // The children of this component are hidden. Don't bubble their
691 + // expiration times.
692 + return;
693 + }
694 +
695 + // Check for pending updates.
696 + let newExpirationTime = NoWork;
697 + switch (workInProgress.tag) {
698 + case HostRoot:
699 + case ClassComponent: {
700 + const updateQueue = workInProgress.updateQueue;
701 + if (updateQueue !== null) {
702 + newExpirationTime = updateQueue.expirationTime;
703 }
704 }
705 + }
706
761 - // TODO: Calls need to visit stateNode
707 + // TODO: Calls need to visit stateNode
708
763 - // Bubble up the earliest expiration time.
764 - // (And "base" render timers if that feature flag is enabled)
765 - if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
766 - let treeBaseTime = workInProgress.selfBaseTime;
767 - let child = workInProgress.child;
768 - while (child !== null) {
769 - treeBaseTime += child.treeBaseTime;
770 - if (
771 - child.expirationTime !== NoWork &&
772 - (newExpirationTime === NoWork ||
773 - newExpirationTime > child.expirationTime)
774 - ) {
775 - newExpirationTime = child.expirationTime;
776 - }
777 - child = child.sibling;
709 + // Bubble up the earliest expiration time.
710 + // (And "base" render timers if that feature flag is enabled)
711 + if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
712 + let treeBaseTime = workInProgress.selfBaseTime;
713 + let child = workInProgress.child;
714 + while (child !== null) {
715 + treeBaseTime += child.treeBaseTime;
716 + if (
717 + child.expirationTime !== NoWork &&
718 + (newExpirationTime === NoWork ||
719 + newExpirationTime > child.expirationTime)
720 + ) {
721 + newExpirationTime = child.expirationTime;
722 }
779 - workInProgress.treeBaseTime = treeBaseTime;
780 - } else {
781 - let child = workInProgress.child;
782 - while (child !== null) {
783 - if (
784 - child.expirationTime !== NoWork &&
785 - (newExpirationTime === NoWork ||
786 - newExpirationTime > child.expirationTime)
787 - ) {
788 - newExpirationTime = child.expirationTime;
789 - }
790 - child = child.sibling;
723 + child = child.sibling;
724 + }
725 + workInProgress.treeBaseTime = treeBaseTime;
726 + } else {
727 + let child = workInProgress.child;
728 + while (child !== null) {
729 + if (
730 + child.expirationTime !== NoWork &&
731 + (newExpirationTime === NoWork ||
732 + newExpirationTime > child.expirationTime)
733 + ) {
734 + newExpirationTime = child.expirationTime;
735 }
736 + child = child.sibling;
737 }
793 -
794 - workInProgress.expirationTime = newExpirationTime;
738 }
739
797 - function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
798 - // Attempt to complete the current unit of work, then move to the
799 - // next sibling. If there are no more siblings, return to the
800 - // parent fiber.
801 - while (true) {
802 - // The current, flushed, state of this fiber is the alternate.
803 - // Ideally nothing should rely on this, but relying on it here
804 - // means that we don't need an additional field on the work in
805 - // progress.
806 - const current = workInProgress.alternate;
740 + workInProgress.expirationTime = newExpirationTime;
741 +}
742 +
743 +function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
744 + // Attempt to complete the current unit of work, then move to the
745 + // next sibling. If there are no more siblings, return to the
746 + // parent fiber.
747 + while (true) {
748 + // The current, flushed, state of this fiber is the alternate.
749 + // Ideally nothing should rely on this, but relying on it here
750 + // means that we don't need an additional field on the work in
751 + // progress.
752 + const current = workInProgress.alternate;
753 + if (__DEV__) {
754 + ReactDebugCurrentFiber.setCurrentFiber(workInProgress);
755 + }
756 +
757 + const returnFiber = workInProgress.return;
758 + const siblingFiber = workInProgress.sibling;
759 +
760 + if ((workInProgress.effectTag & Incomplete) === NoEffect) {
761 + // This fiber completed.
762 + let next = completeWork(
763 + current,
764 + workInProgress,
765 + nextRenderExpirationTime,
766 + );
767 + stopWorkTimer(workInProgress);
768 + resetExpirationTime(workInProgress, nextRenderExpirationTime);
769 if (__DEV__) {
808 - ReactDebugCurrentFiber.setCurrentFiber(workInProgress);
770 + ReactDebugCurrentFiber.resetCurrentFiber();
771 }
772
811 - const returnFiber = workInProgress.return;
812 - const siblingFiber = workInProgress.sibling;
813 -
814 - if ((workInProgress.effectTag & Incomplete) === NoEffect) {
815 - // This fiber completed.
816 - let next = completeWork(
817 - current,
818 - workInProgress,
819 - nextRenderExpirationTime,
820 - );
773 + if (next !== null) {
774 stopWorkTimer(workInProgress);
822 - resetExpirationTime(workInProgress, nextRenderExpirationTime);
823 - if (__DEV__) {
824 - ReactDebugCurrentFiber.resetCurrentFiber();
775 + if (__DEV__ && ReactFiberInstrumentation.debugTool) {
776 + ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
777 }
778 + // If completing this work spawned new work, do that next. We'll come
779 + // back here again.
780 + return next;
781 + }
782
827 - if (next !== null) {
828 - stopWorkTimer(workInProgress);
829 - if (__DEV__ && ReactFiberInstrumentation.debugTool) {
830 - ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
831 - }
832 - // If completing this work spawned new work, do that next. We'll come
833 - // back here again.
834 - return next;
783 + if (
784 + returnFiber !== null &&
785 + // Do not append effects to parents if a sibling failed to complete
786 + (returnFiber.effectTag & Incomplete) === NoEffect
787 + ) {
788 + // Append all the effects of the subtree and this fiber onto the effect
789 + // list of the parent. The completion order of the children affects the
790 + // side-effect order.
791 + if (returnFiber.firstEffect === null) {
792 + returnFiber.firstEffect = workInProgress.firstEffect;
793 }
836 -
837 - if (
838 - returnFiber !== null &&
839 - // Do not append effects to parents if a sibling failed to complete
840 - (returnFiber.effectTag & Incomplete) === NoEffect
841 - ) {
842 - // Append all the effects of the subtree and this fiber onto the effect
843 - // list of the parent. The completion order of the children affects the
844 - // side-effect order.
845 - if (returnFiber.firstEffect === null) {
846 - returnFiber.firstEffect = workInProgress.firstEffect;
847 - }
848 - if (workInProgress.lastEffect !== null) {
849 - if (returnFiber.lastEffect !== null) {
850 - returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
851 - }
852 - returnFiber.lastEffect = workInProgress.lastEffect;
794 + if (workInProgress.lastEffect !== null) {
795 + if (returnFiber.lastEffect !== null) {
796 + returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
797 }
798 + returnFiber.lastEffect = workInProgress.lastEffect;
799 + }
800
855 - // If this fiber had side-effects, we append it AFTER the children's
856 - // side-effects. We can perform certain side-effects earlier if
857 - // needed, by doing multiple passes over the effect list. We don't want
858 - // to schedule our own side-effect on our own list because if end up
859 - // reusing children we'll schedule this effect onto itself since we're
860 - // at the end.
861 - const effectTag = workInProgress.effectTag;
862 - // Skip both NoWork and PerformedWork tags when creating the effect list.
863 - // PerformedWork effect is read by React DevTools but shouldn't be committed.
864 - if (effectTag > PerformedWork) {
865 - if (returnFiber.lastEffect !== null) {
866 - returnFiber.lastEffect.nextEffect = workInProgress;
867 - } else {
868 - returnFiber.firstEffect = workInProgress;
869 - }
870 - returnFiber.lastEffect = workInProgress;
801 + // If this fiber had side-effects, we append it AFTER the children's
802 + // side-effects. We can perform certain side-effects earlier if
803 + // needed, by doing multiple passes over the effect list. We don't want
804 + // to schedule our own side-effect on our own list because if end up
805 + // reusing children we'll schedule this effect onto itself since we're
806 + // at the end.
807 + const effectTag = workInProgress.effectTag;
808 + // Skip both NoWork and PerformedWork tags when creating the effect list.
809 + // PerformedWork effect is read by React DevTools but shouldn't be committed.
810 + if (effectTag > PerformedWork) {
811 + if (returnFiber.lastEffect !== null) {
812 + returnFiber.lastEffect.nextEffect = workInProgress;
813 + } else {
814 + returnFiber.firstEffect = workInProgress;
815 }
816 + returnFiber.lastEffect = workInProgress;
817 }
818 + }
819
874 - if (__DEV__ && ReactFiberInstrumentation.debugTool) {
875 - ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
876 - }
820 + if (__DEV__ && ReactFiberInstrumentation.debugTool) {
821 + ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
822 + }
823
878 - if (siblingFiber !== null) {
879 - // If there is more work to do in this returnFiber, do that next.
880 - return siblingFiber;
881 - } else if (returnFiber !== null) {
882 - // If there's no more work in this returnFiber. Complete the returnFiber.
883 - workInProgress = returnFiber;
884 - continue;
885 - } else {
886 - // We've reached the root.
887 - isRootReadyForCommit = true;
888 - return null;
889 - }
824 + if (siblingFiber !== null) {
825 + // If there is more work to do in this returnFiber, do that next.
826 + return siblingFiber;
827 + } else if (returnFiber !== null) {
828 + // If there's no more work in this returnFiber. Complete the returnFiber.
829 + workInProgress = returnFiber;
830 + continue;
831 } else {
891 - // This fiber did not complete because something threw. Pop values off
892 - // the stack without entering the complete phase. If this is a boundary,
893 - // capture values if possible.
894 - const next = unwindWork(
895 - workInProgress,
896 - nextRenderIsExpired,
897 - nextRenderExpirationTime,
898 - );
899 - // Because this fiber did not complete, don't reset its expiration time.
900 - if (workInProgress.effectTag & DidCapture) {
901 - // Restarting an error boundary
902 - stopFailedWorkTimer(workInProgress);
903 - } else {
904 - stopWorkTimer(workInProgress);
905 - }
906 -
907 - if (__DEV__) {
908 - ReactDebugCurrentFiber.resetCurrentFiber();
909 - }
910 -
911 - if (next !== null) {
912 - stopWorkTimer(workInProgress);
913 - if (__DEV__ && ReactFiberInstrumentation.debugTool) {
914 - ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
915 - }
916 - // If completing this work spawned new work, do that next. We'll come
917 - // back here again.
918 - // Since we're restarting, remove anything that is not a host effect
919 - // from the effect tag.
920 - next.effectTag &= HostEffectMask;
921 - return next;
922 - }
832 + // We've reached the root.
833 + isRootReadyForCommit = true;
834 + return null;
835 + }
836 + } else {
837 + // This fiber did not complete because something threw. Pop values off
838 + // the stack without entering the complete phase. If this is a boundary,
839 + // capture values if possible.
840 + const next = unwindWork(
841 + workInProgress,
842 + nextRenderIsExpired,
843 + nextRenderExpirationTime,
844 + );
845 + // Because this fiber did not complete, don't reset its expiration time.
846 + if (workInProgress.effectTag & DidCapture) {
847 + // Restarting an error boundary
848 + stopFailedWorkTimer(workInProgress);
849 + } else {
850 + stopWorkTimer(workInProgress);
851 + }
852
924 - if (returnFiber !== null) {
925 - // Mark the parent fiber as incomplete and clear its effect list.
926 - returnFiber.firstEffect = returnFiber.lastEffect = null;
927 - returnFiber.effectTag |= Incomplete;
928 - }
853 + if (__DEV__) {
854 + ReactDebugCurrentFiber.resetCurrentFiber();
855 + }
856
857 + if (next !== null) {
858 + stopWorkTimer(workInProgress);
859 if (__DEV__ && ReactFiberInstrumentation.debugTool) {
860 ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
861 }
933 -
934 - if (siblingFiber !== null) {
935 - // If there is more work to do in this returnFiber, do that next.
936 - return siblingFiber;
937 - } else if (returnFiber !== null) {
938 - // If there's no more work in this returnFiber. Complete the returnFiber.
939 - workInProgress = returnFiber;
940 - continue;
941 - } else {
942 - return null;
943 - }
862 + // If completing this work spawned new work, do that next. We'll come
863 + // back here again.
864 + // Since we're restarting, remove anything that is not a host effect
865 + // from the effect tag.
866 + next.effectTag &= HostEffectMask;
867 + return next;
868 }
945 - }
869
947 - // Without this explicit null return Flow complains of invalid return type
948 - // TODO Remove the above while(true) loop
949 - // eslint-disable-next-line no-unreachable
950 - return null;
951 - }
870 + if (returnFiber !== null) {
871 + // Mark the parent fiber as incomplete and clear its effect list.
872 + returnFiber.firstEffect = returnFiber.lastEffect = null;
873 + returnFiber.effectTag |= Incomplete;
874 + }
875
953 - function performUnitOfWork(workInProgress: Fiber): Fiber | null {
954 - // The current, flushed, state of this fiber is the alternate.
955 - // Ideally nothing should rely on this, but relying on it here
956 - // means that we don't need an additional field on the work in
957 - // progress.
958 - const current = workInProgress.alternate;
876 + if (__DEV__ && ReactFiberInstrumentation.debugTool) {
877 + ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
878 + }
879
960 - // See if beginning this work spawns more work.
961 - startWorkTimer(workInProgress);
962 - if (__DEV__) {
963 - ReactDebugCurrentFiber.setCurrentFiber(workInProgress);
880 + if (siblingFiber !== null) {
881 + // If there is more work to do in this returnFiber, do that next.
882 + return siblingFiber;
883 + } else if (returnFiber !== null) {
884 + // If there's no more work in this returnFiber. Complete the returnFiber.
885 + workInProgress = returnFiber;
886 + continue;
887 + } else {
888 + return null;
889 + }
890 }
891 + }
892
966 - if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
967 - stashedWorkInProgressProperties = assignFiberPropertiesInDEV(
968 - stashedWorkInProgressProperties,
969 - workInProgress,
970 - );
971 - }
893 + // Without this explicit null return Flow complains of invalid return type
894 + // TODO Remove the above while(true) loop
895 + // eslint-disable-next-line no-unreachable
896 + return null;
897 +}
898
973 - let next;
974 - if (enableProfilerTimer) {
975 - if (workInProgress.mode & ProfileMode) {
976 - startBaseRenderTimer();
977 - }
899 +function performUnitOfWork(workInProgress: Fiber): Fiber | null {
900 + // The current, flushed, state of this fiber is the alternate.
901 + // Ideally nothing should rely on this, but relying on it here
902 + // means that we don't need an additional field on the work in
903 + // progress.
904 + const current = workInProgress.alternate;
905 +
906 + // See if beginning this work spawns more work.
907 + startWorkTimer(workInProgress);
908 + if (__DEV__) {
909 + ReactDebugCurrentFiber.setCurrentFiber(workInProgress);
910 + }
911
979 - next = beginWork(current, workInProgress, nextRenderExpirationTime);
912 + if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
913 + stashedWorkInProgressProperties = assignFiberPropertiesInDEV(
914 + stashedWorkInProgressProperties,
915 + workInProgress,
916 + );
917 + }
918
981 - if (workInProgress.mode & ProfileMode) {
982 - // Update "base" time if the render wasn't bailed out on.
983 - recordElapsedBaseRenderTimeIfRunning(workInProgress);
984 - stopBaseRenderTimerIfRunning();
985 - }
986 - } else {
987 - next = beginWork(current, workInProgress, nextRenderExpirationTime);
919 + let next;
920 + if (enableProfilerTimer) {
921 + if (workInProgress.mode & ProfileMode) {
922 + startBaseRenderTimer();
923 }
924
990 - if (__DEV__) {
991 - ReactDebugCurrentFiber.resetCurrentFiber();
992 - if (isReplayingFailedUnitOfWork) {
993 - // Currently replaying a failed unit of work. This should be unreachable,
994 - // because the render phase is meant to be idempotent, and it should
995 - // have thrown again. Since it didn't, rethrow the original error, so
996 - // React's internal stack is not misaligned.
997 - rethrowOriginalError();
998 - }
999 - }
1000 - if (__DEV__ && ReactFiberInstrumentation.debugTool) {
1001 - ReactFiberInstrumentation.debugTool.onBeginWork(workInProgress);
1002 - }
925 + next = beginWork(current, workInProgress, nextRenderExpirationTime);
926
1004 - if (next === null) {
1005 - // If this doesn't spawn new work, complete the current work.
1006 - next = completeUnitOfWork(workInProgress);
927 + if (workInProgress.mode & ProfileMode) {
928 + // Update "base" time if the render wasn't bailed out on.
929 + recordElapsedBaseRenderTimeIfRunning(workInProgress);
930 + stopBaseRenderTimerIfRunning();
931 }
932 + } else {
933 + next = beginWork(current, workInProgress, nextRenderExpirationTime);
934 + }
935
1009 - ReactCurrentOwner.current = null;
936 + if (__DEV__) {
937 + ReactDebugCurrentFiber.resetCurrentFiber();
938 + if (isReplayingFailedUnitOfWork) {
939 + // Currently replaying a failed unit of work. This should be unreachable,
940 + // because the render phase is meant to be idempotent, and it should
941 + // have thrown again. Since it didn't, rethrow the original error, so
942 + // React's internal stack is not misaligned.
943 + rethrowOriginalError();
944 + }
945 + }
946 + if (__DEV__ && ReactFiberInstrumentation.debugTool) {
947 + ReactFiberInstrumentation.debugTool.onBeginWork(workInProgress);
948 + }
949
1011 - return next;
950 + if (next === null) {
951 + // If this doesn't spawn new work, complete the current work.
952 + next = completeUnitOfWork(workInProgress);
953 }
954
1014 - function workLoop(isAsync) {
1015 - if (!isAsync) {
1016 - // Flush all expired work.
1017 - while (nextUnitOfWork !== null) {
1018 - nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
1019 - }
1020 - } else {
1021 - // Flush asynchronous work until the deadline runs out of time.
1022 - while (nextUnitOfWork !== null && !shouldYield()) {
1023 - nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
1024 - }
955 + ReactCurrentOwner.current = null;
956
1026 - if (enableProfilerTimer) {
1027 - // If we didn't finish, pause the "actual" render timer.
1028 - // We'll restart it when we resume work.
1029 - pauseActualRenderTimerIfRunning();
1030 - }
957 + return next;
958 +}
959 +
960 +function workLoop(isAsync) {
961 + if (!isAsync) {
962 + // Flush all expired work.
963 + while (nextUnitOfWork !== null) {
964 + nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
965 + }
966 + } else {
967 + // Flush asynchronous work until the deadline runs out of time.
968 + while (nextUnitOfWork !== null && !shouldYield()) {
969 + nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
970 + }
971 +
972 + if (enableProfilerTimer) {
973 + // If we didn't finish, pause the "actual" render timer.
974 + // We'll restart it when we resume work.
975 + pauseActualRenderTimerIfRunning();
976 }
977 }
978 +}
979
1034 - function renderRoot(
1035 - root: FiberRoot,
1036 - expirationTime: ExpirationTime,
1037 - isAsync: boolean,
1038 - ): Fiber | null {
1039 - invariant(
1040 - !isWorking,
1041 - 'renderRoot was called recursively. This error is likely caused ' +
1042 - 'by a bug in React. Please file an issue.',
980 +function renderRoot(
981 + root: FiberRoot,
982 + expirationTime: ExpirationTime,
983 + isAsync: boolean,
984 +): Fiber | null {
985 + invariant(
986 + !isWorking,
987 + 'renderRoot was called recursively. This error is likely caused ' +
988 + 'by a bug in React. Please file an issue.',
989 + );
990 + isWorking = true;
991 +
992 + // Check if we're starting from a fresh stack, or if we're resuming from
993 + // previously yielded work.
994 + if (
995 + expirationTime !== nextRenderExpirationTime ||
996 + root !== nextRoot ||
997 + nextUnitOfWork === null
998 + ) {
999 + // Reset the stack and start working from the root.
1000 + resetStack();
1001 + nextRoot = root;
1002 + nextRenderExpirationTime = expirationTime;
1003 + nextLatestTimeoutMs = -1;
1004 + nextUnitOfWork = createWorkInProgress(
1005 + nextRoot.current,
1006 + null,
1007 + nextRenderExpirationTime,
1008 );
1044 - isWorking = true;
1009 + root.pendingCommitExpirationTime = NoWork;
1010 + }
1011
1046 - // Check if we're starting from a fresh stack, or if we're resuming from
1047 - // previously yielded work.
1048 - if (
1049 - expirationTime !== nextRenderExpirationTime ||
1050 - root !== nextRoot ||
1051 - nextUnitOfWork === null
1052 - ) {
1053 - // Reset the stack and start working from the root.
1054 - resetStack();
1055 - nextRoot = root;
1056 - nextRenderExpirationTime = expirationTime;
1057 - nextLatestTimeoutMs = -1;
1058 - nextUnitOfWork = createWorkInProgress(
1059 - nextRoot.current,
1060 - null,
1061 - nextRenderExpirationTime,
1062 - );
1063 - root.pendingCommitExpirationTime = NoWork;
1064 - }
1012 + let didFatal = false;
1013
1066 - let didFatal = false;
1014 + nextRenderIsExpired =
1015 + !isAsync || nextRenderExpirationTime <= mostRecentCurrentTime;
1016
1068 - nextRenderIsExpired =
1069 - !isAsync || nextRenderExpirationTime <= mostRecentCurrentTime;
1017 + startWorkLoopTimer(nextUnitOfWork);
1018
1071 - startWorkLoopTimer(nextUnitOfWork);
1019 + do {
1020 + try {
1021 + workLoop(isAsync);
1022 + } catch (thrownValue) {
1023 + if (enableProfilerTimer) {
1024 + // Stop "base" render timer in the event of an error.
1025 + stopBaseRenderTimerIfRunning();
1026 + }
1027
1073 - do {
1074 - try {
1075 - workLoop(isAsync);
1076 - } catch (thrownValue) {
1077 - if (enableProfilerTimer) {
1078 - // Stop "base" render timer in the event of an error.
1079 - stopBaseRenderTimerIfRunning();
1028 + if (nextUnitOfWork === null) {
1029 + // This is a fatal error.
1030 + didFatal = true;
1031 + onUncaughtError(thrownValue);
1032 + } else {
1033 + if (__DEV__) {
1034 + // Reset global debug state
1035 + // We assume this is defined in DEV
1036 + (resetCurrentlyProcessingQueue: any)();
1037 }
1038
1082 - if (nextUnitOfWork === null) {
1083 - // This is a fatal error.
1084 - didFatal = true;
1085 - onUncaughtError(thrownValue);
1086 - } else {
1087 - if (__DEV__) {
1088 - // Reset global debug state
1089 - // We assume this is defined in DEV
1090 - (resetCurrentlyProcessingQueue: any)();
1091 - }
1092 -
1093 - const failedUnitOfWork: Fiber = nextUnitOfWork;
1094 - if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
1095 - replayUnitOfWork(failedUnitOfWork, thrownValue, isAsync);
1096 - }
1039 + const failedUnitOfWork: Fiber = nextUnitOfWork;
1040 + if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
1041 + replayUnitOfWork(failedUnitOfWork, thrownValue, isAsync);
1042 + }
1043
1098 - // TODO: we already know this isn't true in some cases.
1099 - // At least this shows a nicer error message until we figure out the cause.
1100 - // https://github.com/facebook/react/issues/12449#issuecomment-386727431
1101 - invariant(
1102 - nextUnitOfWork !== null,
1103 - 'Failed to replay rendering after an error. This ' +
1104 - 'is likely caused by a bug in React. Please file an issue ' +
1105 - 'with a reproducing case to help us find it.',
1106 - );
1044 + // TODO: we already know this isn't true in some cases.
1045 + // At least this shows a nicer error message until we figure out the cause.
1046 + // https://github.com/facebook/react/issues/12449#issuecomment-386727431
1047 + invariant(
1048 + nextUnitOfWork !== null,
1049 + 'Failed to replay rendering after an error. This ' +
1050 + 'is likely caused by a bug in React. Please file an issue ' +
1051 + 'with a reproducing case to help us find it.',
1052 + );
1053
1108 - const sourceFiber: Fiber = nextUnitOfWork;
1109 - let returnFiber = sourceFiber.return;
1110 - if (returnFiber === null) {
1111 - // This is the root. The root could capture its own errors. However,
1112 - // we don't know if it errors before or after we pushed the host
1113 - // context. This information is needed to avoid a stack mismatch.
1114 - // Because we're not sure, treat this as a fatal error. We could track
1115 - // which phase it fails in, but doesn't seem worth it. At least
1116 - // for now.
1117 - didFatal = true;
1118 - onUncaughtError(thrownValue);
1119 - break;
1120 - }
1121 - throwException(
1122 - root,
1123 - returnFiber,
1124 - sourceFiber,
1125 - thrownValue,
1126 - nextRenderIsExpired,
1127 - nextRenderExpirationTime,
1128 - mostRecentCurrentTimeMs,
1129 - );
1130 - nextUnitOfWork = completeUnitOfWork(sourceFiber);
1054 + const sourceFiber: Fiber = nextUnitOfWork;
1055 + let returnFiber = sourceFiber.return;
1056 + if (returnFiber === null) {
1057 + // This is the root. The root could capture its own errors. However,
1058 + // we don't know if it errors before or after we pushed the host
1059 + // context. This information is needed to avoid a stack mismatch.
1060 + // Because we're not sure, treat this as a fatal error. We could track
1061 + // which phase it fails in, but doesn't seem worth it. At least
1062 + // for now.
1063 + didFatal = true;
1064 + onUncaughtError(thrownValue);
1065 + break;
1066 }
1067 + throwException(
1068 + root,
1069 + returnFiber,
1070 + sourceFiber,
1071 + thrownValue,
1072 + nextRenderIsExpired,
1073 + nextRenderExpirationTime,
1074 + mostRecentCurrentTimeMs,
1075 + );
1076 + nextUnitOfWork = completeUnitOfWork(sourceFiber);
1077 }
1133 - break;
1134 - } while (true);
1078 + }
1079 + break;
1080 + } while (true);
1081
1136 - // We're done performing work. Time to clean up.
1137 - let didCompleteRoot = false;
1138 - isWorking = false;
1082 + // We're done performing work. Time to clean up.
1083 + let didCompleteRoot = false;
1084 + isWorking = false;
1085
1140 - // Yield back to main thread.
1141 - if (didFatal) {
1086 + // Yield back to main thread.
1087 + if (didFatal) {
1088 + stopWorkLoopTimer(interruptedBy, didCompleteRoot);
1089 + interruptedBy = null;
1090 + // There was a fatal error.
1091 + if (__DEV__) {
1092 + resetStackAfterFatalErrorInDev();
1093 + }
1094 + return null;
1095 + } else if (nextUnitOfWork === null) {
1096 + // We reached the root.
1097 + if (isRootReadyForCommit) {
1098 + didCompleteRoot = true;
1099 stopWorkLoopTimer(interruptedBy, didCompleteRoot);
1100 interruptedBy = null;
1144 - // There was a fatal error.
1145 - if (__DEV__) {
1146 - stack.resetStackAfterFatalErrorInDev();
1147 - }
1148 - return null;
1149 - } else if (nextUnitOfWork === null) {
1150 - // We reached the root.
1151 - if (isRootReadyForCommit) {
1152 - didCompleteRoot = true;
1153 - stopWorkLoopTimer(interruptedBy, didCompleteRoot);
1154 - interruptedBy = null;
1155 - // The root successfully completed. It's ready for commit.
1156 - root.pendingCommitExpirationTime = expirationTime;
1157 - const finishedWork = root.current.alternate;
1158 - return finishedWork;
1159 - } else {
1160 - // The root did not complete.
1161 - stopWorkLoopTimer(interruptedBy, didCompleteRoot);
1162 - interruptedBy = null;
1163 - invariant(
1164 - !nextRenderIsExpired,
1165 - 'Expired work should have completed. This error is likely caused ' +
1166 - 'by a bug in React. Please file an issue.',
1167 - );
1168 - markSuspendedPriorityLevel(root, expirationTime);
1169 - if (nextLatestTimeoutMs >= 0) {
1170 - setTimeout(() => {
1171 - retrySuspendedRoot(root, expirationTime);
1172 - }, nextLatestTimeoutMs);
1173 - }
1174 - const firstUnblockedExpirationTime = findNextPendingPriorityLevel(root);
1175 - onBlock(firstUnblockedExpirationTime);
1176 - return null;
1177 - }
1101 + // The root successfully completed. It's ready for commit.
1102 + root.pendingCommitExpirationTime = expirationTime;
1103 + const finishedWork = root.current.alternate;
1104 + return finishedWork;
1105 } else {
1106 + // The root did not complete.
1107 stopWorkLoopTimer(interruptedBy, didCompleteRoot);
1108 interruptedBy = null;
1181 - // There's more work to do, but we ran out of time. Yield back to
1182 - // the renderer.
1109 + invariant(
1110 + !nextRenderIsExpired,
1111 + 'Expired work should have completed. This error is likely caused ' +
1112 + 'by a bug in React. Please file an issue.',
1113 + );
1114 + markSuspendedPriorityLevel(root, expirationTime);
1115 + if (nextLatestTimeoutMs >= 0) {
1116 + setTimeout(() => {
1117 + retrySuspendedRoot(root, expirationTime);
1118 + }, nextLatestTimeoutMs);
1119 + }
1120 + const firstUnblockedExpirationTime = findNextPendingPriorityLevel(root);
1121 + onBlock(firstUnblockedExpirationTime);
1122 return null;
1123 }
1124 + } else {
1125 + stopWorkLoopTimer(interruptedBy, didCompleteRoot);
1126 + interruptedBy = null;
1127 + // There's more work to do, but we ran out of time. Yield back to
1128 + // the renderer.
1129 + return null;
1130 }
1131 +}
1132
1187 - function dispatch(
1188 - sourceFiber: Fiber,
1189 - value: mixed,
1190 - expirationTime: ExpirationTime,
1191 - ) {
1192 - invariant(
1193 - !isWorking || isCommitting,
1194 - 'dispatch: Cannot dispatch during the render phase.',
1195 - );
1133 +function dispatch(
1134 + sourceFiber: Fiber,
1135 + value: mixed,
1136 + expirationTime: ExpirationTime,
1137 +) {
1138 + invariant(
1139 + !isWorking || isCommitting,
1140 + 'dispatch: Cannot dispatch during the render phase.',
1141 + );
1142
1197 - let fiber = sourceFiber.return;
1198 - while (fiber !== null) {
1199 - switch (fiber.tag) {
1200 - case ClassComponent:
1201 - const ctor = fiber.type;
1202 - const instance = fiber.stateNode;
1203 - if (
1204 - typeof ctor.getDerivedStateFromCatch === 'function' ||
1205 - (typeof instance.componentDidCatch === 'function' &&
1206 - !isAlreadyFailedLegacyErrorBoundary(instance))
1207 - ) {
1208 - const errorInfo = createCapturedValue(value, sourceFiber);
1209 - const update = createClassErrorUpdate(
1210 - fiber,
1211 - errorInfo,
1212 - expirationTime,
1213 - );
1214 - enqueueUpdate(fiber, update, expirationTime);
1215 - scheduleWork(fiber, expirationTime);
1216 - return;
1217 - }
1218 - break;
1219 - case HostRoot: {
1143 + let fiber = sourceFiber.return;
1144 + while (fiber !== null) {
1145 + switch (fiber.tag) {
1146 + case ClassComponent:
1147 + const ctor = fiber.type;
1148 + const instance = fiber.stateNode;
1149 + if (
1150 + typeof ctor.getDerivedStateFromCatch === 'function' ||
1151 + (typeof instance.componentDidCatch === 'function' &&
1152 + !isAlreadyFailedLegacyErrorBoundary(instance))
1153 + ) {
1154 const errorInfo = createCapturedValue(value, sourceFiber);
1221 - const update = createRootErrorUpdate(
1155 + const update = createClassErrorUpdate(
1156 fiber,
1157 errorInfo,
1158 expirationTime,
@@ -1227,815 +1161,820 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
1161 scheduleWork(fiber, expirationTime);
1162 return;
1163 }
1164 + break;
1165 + case HostRoot: {
1166 + const errorInfo = createCapturedValue(value, sourceFiber);
1167 + const update = createRootErrorUpdate(fiber, errorInfo, expirationTime);
1168 + enqueueUpdate(fiber, update, expirationTime);
1169 + scheduleWork(fiber, expirationTime);
1170 + return;
1171 }
1231 - fiber = fiber.return;
1232 - }
1233 -
1234 - if (sourceFiber.tag === HostRoot) {
1235 - // Error was thrown at the root. There is no parent, so the root
1236 - // itself should capture it.
1237 - const rootFiber = sourceFiber;
1238 - const errorInfo = createCapturedValue(value, rootFiber);
1239 - const update = createRootErrorUpdate(
1240 - rootFiber,
1241 - errorInfo,
1242 - expirationTime,
1243 - );
1244 - enqueueUpdate(rootFiber, update, expirationTime);
1245 - scheduleWork(rootFiber, expirationTime);
1172 }
1173 + fiber = fiber.return;
1174 }
1175
1249 - function onCommitPhaseError(fiber: Fiber, error: mixed) {
1250 - return dispatch(fiber, error, Sync);
1176 + if (sourceFiber.tag === HostRoot) {
1177 + // Error was thrown at the root. There is no parent, so the root
1178 + // itself should capture it.
1179 + const rootFiber = sourceFiber;
1180 + const errorInfo = createCapturedValue(value, rootFiber);
1181 + const update = createRootErrorUpdate(rootFiber, errorInfo, expirationTime);
1182 + enqueueUpdate(rootFiber, update, expirationTime);
1183 + scheduleWork(rootFiber, expirationTime);
1184 }
1185 +}
1186 +
1187 +function captureCommitPhaseError(fiber: Fiber, error: mixed) {
1188 + return dispatch(fiber, error, Sync);
1189 +}
1190
1253 - function computeAsyncExpiration(currentTime: ExpirationTime) {
1254 - // Given the current clock time, returns an expiration time. We use rounding
1255 - // to batch like updates together.
1256 - // Should complete within ~1000ms. 1200ms max.
1257 - const expirationMs = 5000;
1258 - const bucketSizeMs = 250;
1259 - return computeExpirationBucket(currentTime, expirationMs, bucketSizeMs);
1191 +function computeAsyncExpiration(currentTime: ExpirationTime) {
1192 + // Given the current clock time, returns an expiration time. We use rounding
1193 + // to batch like updates together.
1194 + // Should complete within ~1000ms. 1200ms max.
1195 + const expirationMs = 5000;
1196 + const bucketSizeMs = 250;
1197 + return computeExpirationBucket(currentTime, expirationMs, bucketSizeMs);
1198 +}
1199 +
1200 +function computeInteractiveExpiration(currentTime: ExpirationTime) {
1201 + let expirationMs;
1202 + // We intentionally set a higher expiration time for interactive updates in
1203 + // dev than in production.
1204 + // If the main thread is being blocked so long that you hit the expiration,
1205 + // it's a problem that could be solved with better scheduling.
1206 + // People will be more likely to notice this and fix it with the long
1207 + // expiration time in development.
1208 + // In production we opt for better UX at the risk of masking scheduling
1209 + // problems, by expiring fast.
1210 + if (__DEV__) {
1211 + // Should complete within ~500ms. 600ms max.
1212 + expirationMs = 500;
1213 + } else {
1214 + // In production things should be more responsive, 150ms max.
1215 + expirationMs = 150;
1216 }
1217 + const bucketSizeMs = 100;
1218 + return computeExpirationBucket(currentTime, expirationMs, bucketSizeMs);
1219 +}
1220
1262 - function computeInteractiveExpiration(currentTime: ExpirationTime) {
1263 - let expirationMs;
1264 - // We intentionally set a higher expiration time for interactive updates in
1265 - // dev than in production.
1266 - // If the main thread is being blocked so long that you hit the expiration,
1267 - // it's a problem that could be solved with better scheduling.
1268 - // People will be more likely to notice this and fix it with the long
1269 - // expiration time in development.
1270 - // In production we opt for better UX at the risk of masking scheduling
1271 - // problems, by expiring fast.
1272 - if (__DEV__) {
1273 - // Should complete within ~500ms. 600ms max.
1274 - expirationMs = 500;
1275 - } else {
1276 - // In production things should be more responsive, 150ms max.
1277 - expirationMs = 150;
1278 - }
1279 - const bucketSizeMs = 100;
1280 - return computeExpirationBucket(currentTime, expirationMs, bucketSizeMs);
1281 - }
1282 -
1283 - // Creates a unique async expiration time.
1284 - function computeUniqueAsyncExpiration(): ExpirationTime {
1285 - const currentTime = recalculateCurrentTime();
1286 - let result = computeAsyncExpiration(currentTime);
1287 - if (result <= lastUniqueAsyncExpiration) {
1288 - // Since we assume the current time monotonically increases, we only hit
1289 - // this branch when computeUniqueAsyncExpiration is fired multiple times
1290 - // within a 200ms window (or whatever the async bucket size is).
1291 - result = lastUniqueAsyncExpiration + 1;
1292 - }
1293 - lastUniqueAsyncExpiration = result;
1294 - return lastUniqueAsyncExpiration;
1221 +// Creates a unique async expiration time.
1222 +function computeUniqueAsyncExpiration(): ExpirationTime {
1223 + const currentTime = recalculateCurrentTime();
1224 + let result = computeAsyncExpiration(currentTime);
1225 + if (result <= lastUniqueAsyncExpiration) {
1226 + // Since we assume the current time monotonically increases, we only hit
1227 + // this branch when computeUniqueAsyncExpiration is fired multiple times
1228 + // within a 200ms window (or whatever the async bucket size is).
1229 + result = lastUniqueAsyncExpiration + 1;
1230 }
1231 + lastUniqueAsyncExpiration = result;
1232 + return lastUniqueAsyncExpiration;
1233 +}
1234
1297 - function computeExpirationForFiber(
1298 - currentTime: ExpirationTime,
1299 - fiber: Fiber,
1300 - ) {
1301 - let expirationTime;
1302 - if (expirationContext !== NoWork) {
1303 - // An explicit expiration context was set;
1304 - expirationTime = expirationContext;
1305 - } else if (isWorking) {
1306 - if (isCommitting) {
1307 - // Updates that occur during the commit phase should have sync priority
1308 - // by default.
1309 - expirationTime = Sync;
1310 - } else {
1311 - // Updates during the render phase should expire at the same time as
1312 - // the work that is being rendered.
1313 - expirationTime = nextRenderExpirationTime;
1314 - }
1235 +function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {
1236 + let expirationTime;
1237 + if (expirationContext !== NoWork) {
1238 + // An explicit expiration context was set;
1239 + expirationTime = expirationContext;
1240 + } else if (isWorking) {
1241 + if (isCommitting) {
1242 + // Updates that occur during the commit phase should have sync priority
1243 + // by default.
1244 + expirationTime = Sync;
1245 } else {
1316 - // No explicit expiration context was set, and we're not currently
1317 - // performing work. Calculate a new expiration time.
1318 - if (fiber.mode & AsyncMode) {
1319 - if (isBatchingInteractiveUpdates) {
1320 - // This is an interactive update
1321 - expirationTime = computeInteractiveExpiration(currentTime);
1322 - } else {
1323 - // This is an async update
1324 - expirationTime = computeAsyncExpiration(currentTime);
1325 - }
1246 + // Updates during the render phase should expire at the same time as
1247 + // the work that is being rendered.
1248 + expirationTime = nextRenderExpirationTime;
1249 + }
1250 + } else {
1251 + // No explicit expiration context was set, and we're not currently
1252 + // performing work. Calculate a new expiration time.
1253 + if (fiber.mode & AsyncMode) {
1254 + if (isBatchingInteractiveUpdates) {
1255 + // This is an interactive update
1256 + expirationTime = computeInteractiveExpiration(currentTime);
1257 } else {
1327 - // This is a sync update
1328 - expirationTime = Sync;
1258 + // This is an async update
1259 + expirationTime = computeAsyncExpiration(currentTime);
1260 }
1261 + } else {
1262 + // This is a sync update
1263 + expirationTime = Sync;
1264 }
1331 - if (isBatchingInteractiveUpdates) {
1332 - // This is an interactive update. Keep track of the lowest pending
1333 - // interactive expiration time. This allows us to synchronously flush
1334 - // all interactive updates when needed.
1335 - if (
1336 - lowestPendingInteractiveExpirationTime === NoWork ||
1337 - expirationTime > lowestPendingInteractiveExpirationTime
1338 - ) {
1339 - lowestPendingInteractiveExpirationTime = expirationTime;
1340 - }
1265 + }
1266 + if (isBatchingInteractiveUpdates) {
1267 + // This is an interactive update. Keep track of the lowest pending
1268 + // interactive expiration time. This allows us to synchronously flush
1269 + // all interactive updates when needed.
1270 + if (
1271 + lowestPendingInteractiveExpirationTime === NoWork ||
1272 + expirationTime > lowestPendingInteractiveExpirationTime
1273 + ) {
1274 + lowestPendingInteractiveExpirationTime = expirationTime;
1275 }
1342 - return expirationTime;
1276 }
1277 + return expirationTime;
1278 +}
1279
1345 - // TODO: Rename this to scheduleTimeout or something
1346 - function suspendRoot(
1347 - root: FiberRoot,
1348 - thenable: Thenable,
1349 - timeoutMs: number,
1350 - suspendedTime: ExpirationTime,
1351 - ) {
1352 - // Schedule the timeout.
1353 - if (timeoutMs >= 0 && nextLatestTimeoutMs < timeoutMs) {
1354 - nextLatestTimeoutMs = timeoutMs;
1355 - }
1280 +// TODO: Rename this to scheduleTimeout or something
1281 +function suspendRoot(
1282 + root: FiberRoot,
1283 + thenable: Thenable,
1284 + timeoutMs: number,
1285 + suspendedTime: ExpirationTime,
1286 +) {
1287 + // Schedule the timeout.
1288 + if (timeoutMs >= 0 && nextLatestTimeoutMs < timeoutMs) {
1289 + nextLatestTimeoutMs = timeoutMs;
1290 }
1291 +}
1292
1358 - function retrySuspendedRoot(root, suspendedTime) {
1359 - markPingedPriorityLevel(root, suspendedTime);
1360 - const retryTime = findNextPendingPriorityLevel(root);
1361 - if (retryTime !== NoWork) {
1362 - requestRetry(root, retryTime);
1363 - }
1293 +function retrySuspendedRoot(root: FiberRoot, suspendedTime: ExpirationTime) {
1294 + markPingedPriorityLevel(root, suspendedTime);
1295 + const retryTime = findNextPendingPriorityLevel(root);
1296 + if (retryTime !== NoWork) {
1297 + requestRetry(root, retryTime);
1298 }
1299 +}
1300
1366 - function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
1367 - recordScheduleUpdate();
1301 +function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
1302 + recordScheduleUpdate();
1303
1369 - if (__DEV__) {
1370 - if (fiber.tag === ClassComponent) {
1371 - const instance = fiber.stateNode;
1372 - warnAboutInvalidUpdates(instance);
1373 - }
1304 + if (__DEV__) {
1305 + if (fiber.tag === ClassComponent) {
1306 + const instance = fiber.stateNode;
1307 + warnAboutInvalidUpdates(instance);
1308 }
1309 + }
1310
1376 - let node = fiber;
1377 - while (node !== null) {
1378 - // Walk the parent path to the root and update each node's
1379 - // expiration time.
1311 + let node = fiber;
1312 + while (node !== null) {
1313 + // Walk the parent path to the root and update each node's
1314 + // expiration time.
1315 + if (
1316 + node.expirationTime === NoWork ||
1317 + node.expirationTime > expirationTime
1318 + ) {
1319 + node.expirationTime = expirationTime;
1320 + }
1321 + if (node.alternate !== null) {
1322 if (
1381 - node.expirationTime === NoWork ||
1382 - node.expirationTime > expirationTime
1323 + node.alternate.expirationTime === NoWork ||
1324 + node.alternate.expirationTime > expirationTime
1325 ) {
1384 - node.expirationTime = expirationTime;
1326 + node.alternate.expirationTime = expirationTime;
1327 }
1386 - if (node.alternate !== null) {
1328 + }
1329 + if (node.return === null) {
1330 + if (node.tag === HostRoot) {
1331 + const root: FiberRoot = (node.stateNode: any);
1332 if (
1388 - node.alternate.expirationTime === NoWork ||
1389 - node.alternate.expirationTime > expirationTime
1333 + !isWorking &&
1334 + nextRenderExpirationTime !== NoWork &&
1335 + expirationTime < nextRenderExpirationTime
1336 ) {
1391 - node.alternate.expirationTime = expirationTime;
1337 + // This is an interruption. (Used for performance tracking.)
1338 + interruptedBy = fiber;
1339 + resetStack();
1340 }
1393 - }
1394 - if (node.return === null) {
1395 - if (node.tag === HostRoot) {
1396 - const root: FiberRoot = (node.stateNode: any);
1397 - if (
1398 - !isWorking &&
1399 - nextRenderExpirationTime !== NoWork &&
1400 - expirationTime < nextRenderExpirationTime
1401 - ) {
1402 - // This is an interruption. (Used for performance tracking.)
1403 - interruptedBy = fiber;
1404 - resetStack();
1405 - }
1406 - markPendingPriorityLevel(root, expirationTime);
1407 - const nextExpirationTimeToWorkOn = findNextPendingPriorityLevel(root);
1408 - if (
1409 - // If we're in the render phase, we don't need to schedule this root
1410 - // for an update, because we'll do it before we exit...
1411 - !isWorking ||
1412 - isCommitting ||
1413 - // ...unless this is a different root than the one we're rendering.
1414 - nextRoot !== root
1415 - ) {
1416 - requestWork(root, nextExpirationTimeToWorkOn);
1417 - }
1418 - if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
1419 - invariant(
1420 - false,
1421 - 'Maximum update depth exceeded. This can happen when a ' +
1422 - 'component repeatedly calls setState inside ' +
1423 - 'componentWillUpdate or componentDidUpdate. React limits ' +
1424 - 'the number of nested updates to prevent infinite loops.',
1425 - );
1426 - }
1427 - } else {
1428 - if (__DEV__) {
1429 - if (fiber.tag === ClassComponent) {
1430 - warnAboutUpdateOnUnmounted(fiber);
1431 - }
1341 + markPendingPriorityLevel(root, expirationTime);
1342 + const nextExpirationTimeToWorkOn = findNextPendingPriorityLevel(root);
1343 + if (
1344 + // If we're in the render phase, we don't need to schedule this root
1345 + // for an update, because we'll do it before we exit...
1346 + !isWorking ||
1347 + isCommitting ||
1348 + // ...unless this is a different root than the one we're rendering.
1349 + nextRoot !== root
1350 + ) {
1351 + requestWork(root, nextExpirationTimeToWorkOn);
1352 + }
1353 + if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
1354 + invariant(
1355 + false,
1356 + 'Maximum update depth exceeded. This can happen when a ' +
1357 + 'component repeatedly calls setState inside ' +
1358 + 'componentWillUpdate or componentDidUpdate. React limits ' +
1359 + 'the number of nested updates to prevent infinite loops.',
1360 + );
1361 + }
1362 + } else {
1363 + if (__DEV__) {
1364 + if (fiber.tag === ClassComponent) {
1365 + warnAboutUpdateOnUnmounted(fiber);
1366 }
1433 - return;
1367 }
1368 + return;
1369 }
1436 - node = node.return;
1370 }
1371 + node = node.return;
1372 }
1373 +}
1374
1440 - function recalculateCurrentTime(): ExpirationTime {
1441 - // Subtract initial time so it fits inside 32bits
1442 - mostRecentCurrentTimeMs = now() - originalStartTimeMs;
1443 - mostRecentCurrentTime = msToExpirationTime(mostRecentCurrentTimeMs);
1444 - return mostRecentCurrentTime;
1445 - }
1375 +function recalculateCurrentTime(): ExpirationTime {
1376 + // Subtract initial time so it fits inside 32bits
1377 + mostRecentCurrentTimeMs = now() - originalStartTimeMs;
1378 + mostRecentCurrentTime = msToExpirationTime(mostRecentCurrentTimeMs);
1379 + return mostRecentCurrentTime;
1380 +}
1381
1447 - function deferredUpdates<A>(fn: () => A): A {
1448 - const previousExpirationContext = expirationContext;
1449 - const currentTime = recalculateCurrentTime();
1450 - expirationContext = computeAsyncExpiration(currentTime);
1451 - try {
1452 - return fn();
1453 - } finally {
1454 - expirationContext = previousExpirationContext;
1455 - }
1382 +function deferredUpdates<A>(fn: () => A): A {
1383 + const previousExpirationContext = expirationContext;
1384 + const currentTime = recalculateCurrentTime();
1385 + expirationContext = computeAsyncExpiration(currentTime);
1386 + try {
1387 + return fn();
1388 + } finally {
1389 + expirationContext = previousExpirationContext;
1390 }
1457 - function syncUpdates<A, B, C0, D, R>(
1458 - fn: (A, B, C0, D) => R,
1459 - a: A,
1460 - b: B,
1461 - c: C0,
1462 - d: D,
1463 - ): R {
1464 - const previousExpirationContext = expirationContext;
1465 - expirationContext = Sync;
1466 - try {
1467 - return fn(a, b, c, d);
1468 - } finally {
1469 - expirationContext = previousExpirationContext;
1470 - }
1391 +}
1392 +function syncUpdates<A, B, C0, D, R>(
1393 + fn: (A, B, C0, D) => R,
1394 + a: A,
1395 + b: B,
1396 + c: C0,
1397 + d: D,
1398 +): R {
1399 + const previousExpirationContext = expirationContext;
1400 + expirationContext = Sync;
1401 + try {
1402 + return fn(a, b, c, d);
1403 + } finally {
1404 + expirationContext = previousExpirationContext;
1405 }
1406 +}
1407
1473 - // TODO: Everything below this is written as if it has been lifted to the
1474 - // renderers. I'll do this in a follow-up.
1475 -
1476 - // Linked-list of roots
1477 - let firstScheduledRoot: FiberRoot | null = null;
1478 - let lastScheduledRoot: FiberRoot | null = null;
1479 -
1480 - let callbackExpirationTime: ExpirationTime = NoWork;
1481 - let callbackID: number = -1;
1482 - let isRendering: boolean = false;
1483 - let nextFlushedRoot: FiberRoot | null = null;
1484 - let nextFlushedExpirationTime: ExpirationTime = NoWork;
1485 - let lowestPendingInteractiveExpirationTime: ExpirationTime = NoWork;
1486 - let deadlineDidExpire: boolean = false;
1487 - let hasUnhandledError: boolean = false;
1488 - let unhandledError: mixed | null = null;
1489 - let deadline: Deadline | null = null;
1490 -
1491 - let isBatchingUpdates: boolean = false;
1492 - let isUnbatchingUpdates: boolean = false;
1493 - let isBatchingInteractiveUpdates: boolean = false;
1494 -
1495 - let completedBatches: Array<Batch> | null = null;
1496 -
1497 - // Use these to prevent an infinite loop of nested updates
1498 - const NESTED_UPDATE_LIMIT = 1000;
1499 - let nestedUpdateCount: number = 0;
1500 -
1501 - const timeHeuristicForUnitOfWork = 1;
1502 -
1503 - function scheduleCallbackWithExpiration(expirationTime) {
1504 - if (callbackExpirationTime !== NoWork) {
1505 - // A callback is already scheduled. Check its expiration time (timeout).
1506 - if (expirationTime > callbackExpirationTime) {
1507 - // Existing callback has sufficient timeout. Exit.
1508 - return;
1509 - } else {
1510 - // Existing callback has insufficient timeout. Cancel and schedule a
1511 - // new one.
1512 - cancelDeferredCallback(callbackID);
1513 - }
1514 - // The request callback timer is already running. Don't start a new one.
1408 +// TODO: Everything below this is written as if it has been lifted to the
1409 +// renderers. I'll do this in a follow-up.
1410 +
1411 +// Linked-list of roots
1412 +let firstScheduledRoot: FiberRoot | null = null;
1413 +let lastScheduledRoot: FiberRoot | null = null;
1414 +
1415 +let callbackExpirationTime: ExpirationTime = NoWork;
1416 +let callbackID: number = -1;
1417 +let isRendering: boolean = false;
1418 +let nextFlushedRoot: FiberRoot | null = null;
1419 +let nextFlushedExpirationTime: ExpirationTime = NoWork;
1420 +let lowestPendingInteractiveExpirationTime: ExpirationTime = NoWork;
1421 +let deadlineDidExpire: boolean = false;
1422 +let hasUnhandledError: boolean = false;
1423 +let unhandledError: mixed | null = null;
1424 +let deadline: Deadline | null = null;
1425 +
1426 +let isBatchingUpdates: boolean = false;
1427 +let isUnbatchingUpdates: boolean = false;
1428 +let isBatchingInteractiveUpdates: boolean = false;
1429 +
1430 +let completedBatches: Array<Batch> | null = null;
1431 +
1432 +// Use these to prevent an infinite loop of nested updates
1433 +const NESTED_UPDATE_LIMIT = 1000;
1434 +let nestedUpdateCount: number = 0;
1435 +
1436 +const timeHeuristicForUnitOfWork = 1;
1437 +
1438 +function scheduleCallbackWithExpiration(expirationTime) {
1439 + if (callbackExpirationTime !== NoWork) {
1440 + // A callback is already scheduled. Check its expiration time (timeout).
1441 + if (expirationTime > callbackExpirationTime) {
1442 + // Existing callback has sufficient timeout. Exit.
1443 + return;
1444 } else {
1516 - startRequestCallbackTimer();
1445 + // Existing callback has insufficient timeout. Cancel and schedule a
1446 + // new one.
1447 + cancelDeferredCallback(callbackID);
1448 }
1449 + // The request callback timer is already running. Don't start a new one.
1450 + } else {
1451 + startRequestCallbackTimer();
1452 + }
1453
1519 - // Compute a timeout for the given expiration time.
1520 - const currentMs = now() - originalStartTimeMs;
1521 - const expirationMs = expirationTimeToMs(expirationTime);
1522 - const timeout = expirationMs - currentMs;
1454 + // Compute a timeout for the given expiration time.
1455 + const currentMs = now() - originalStartTimeMs;
1456 + const expirationMs = expirationTimeToMs(expirationTime);
1457 + const timeout = expirationMs - currentMs;
1458
1524 - callbackExpirationTime = expirationTime;
1525 - callbackID = scheduleDeferredCallback(performAsyncWork, {timeout});
1526 - }
1459 + callbackExpirationTime = expirationTime;
1460 + callbackID = scheduleDeferredCallback(performAsyncWork, {timeout});
1461 +}
1462
1528 - function requestRetry(root: FiberRoot, expirationTime: ExpirationTime) {
1529 - if (
1530 - root.remainingExpirationTime === NoWork ||
1531 - root.remainingExpirationTime < expirationTime
1532 - ) {
1533 - // For a retry, only update the remaining expiration time if it has a
1534 - // *lower priority* than the existing value. This is because, on a retry,
1535 - // we should attempt to coalesce as much as possible.
1536 - requestWork(root, expirationTime);
1537 - }
1463 +function requestRetry(root: FiberRoot, expirationTime: ExpirationTime) {
1464 + if (
1465 + root.remainingExpirationTime === NoWork ||
1466 + root.remainingExpirationTime < expirationTime
1467 + ) {
1468 + // For a retry, only update the remaining expiration time if it has a
1469 + // *lower priority* than the existing value. This is because, on a retry,
1470 + // we should attempt to coalesce as much as possible.
1471 + requestWork(root, expirationTime);
1472 }
1473 +}
1474
1540 - // requestWork is called by the scheduler whenever a root receives an update.
1541 - // It's up to the renderer to call renderRoot at some point in the future.
1542 - function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
1543 - addRootToSchedule(root, expirationTime);
1475 +// requestWork is called by the scheduler whenever a root receives an update.
1476 +// It's up to the renderer to call renderRoot at some point in the future.
1477 +function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
1478 + addRootToSchedule(root, expirationTime);
1479
1545 - if (isRendering) {
1546 - // Prevent reentrancy. Remaining work will be scheduled at the end of
1547 - // the currently rendering batch.
1548 - return;
1549 - }
1480 + if (isRendering) {
1481 + // Prevent reentrancy. Remaining work will be scheduled at the end of
1482 + // the currently rendering batch.
1483 + return;
1484 + }
1485
1551 - if (isBatchingUpdates) {
1552 - // Flush work at the end of the batch.
1553 - if (isUnbatchingUpdates) {
1554 - // ...unless we're inside unbatchedUpdates, in which case we should
1555 - // flush it now.
1556 - nextFlushedRoot = root;
1557 - nextFlushedExpirationTime = Sync;
1558 - performWorkOnRoot(root, Sync, false);
1559 - }
1560 - return;
1486 + if (isBatchingUpdates) {
1487 + // Flush work at the end of the batch.
1488 + if (isUnbatchingUpdates) {
1489 + // ...unless we're inside unbatchedUpdates, in which case we should
1490 + // flush it now.
1491 + nextFlushedRoot = root;
1492 + nextFlushedExpirationTime = Sync;
1493 + performWorkOnRoot(root, Sync, false);
1494 }
1495 + return;
1496 + }
1497
1563 - // TODO: Get rid of Sync and use current time?
1564 - if (expirationTime === Sync) {
1565 - performSyncWork();
1566 - } else {
1567 - scheduleCallbackWithExpiration(expirationTime);
1568 - }
1498 + // TODO: Get rid of Sync and use current time?
1499 + if (expirationTime === Sync) {
1500 + performSyncWork();
1501 + } else {
1502 + scheduleCallbackWithExpiration(expirationTime);
1503 }
1504 +}
1505
1571 - function addRootToSchedule(root: FiberRoot, expirationTime: ExpirationTime) {
1572 - // Add the root to the schedule.
1573 - // Check if this root is already part of the schedule.
1574 - if (root.nextScheduledRoot === null) {
1575 - // This root is not already scheduled. Add it.
1576 - root.remainingExpirationTime = expirationTime;
1577 - if (lastScheduledRoot === null) {
1578 - firstScheduledRoot = lastScheduledRoot = root;
1579 - root.nextScheduledRoot = root;
1580 - } else {
1581 - lastScheduledRoot.nextScheduledRoot = root;
1582 - lastScheduledRoot = root;
1583 - lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
1584 - }
1506 +function addRootToSchedule(root: FiberRoot, expirationTime: ExpirationTime) {
1507 + // Add the root to the schedule.
1508 + // Check if this root is already part of the schedule.
1509 + if (root.nextScheduledRoot === null) {
1510 + // This root is not already scheduled. Add it.
1511 + root.remainingExpirationTime = expirationTime;
1512 + if (lastScheduledRoot === null) {
1513 + firstScheduledRoot = lastScheduledRoot = root;
1514 + root.nextScheduledRoot = root;
1515 } else {
1586 - // This root is already scheduled, but its priority may have increased.
1587 - const remainingExpirationTime = root.remainingExpirationTime;
1588 - if (
1589 - remainingExpirationTime === NoWork ||
1590 - expirationTime < remainingExpirationTime
1591 - ) {
1592 - // Update the priority.
1593 - root.remainingExpirationTime = expirationTime;
1594 - }
1516 + lastScheduledRoot.nextScheduledRoot = root;
1517 + lastScheduledRoot = root;
1518 + lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
1519 + }
1520 + } else {
1521 + // This root is already scheduled, but its priority may have increased.
1522 + const remainingExpirationTime = root.remainingExpirationTime;
1523 + if (
1524 + remainingExpirationTime === NoWork ||
1525 + expirationTime < remainingExpirationTime
1526 + ) {
1527 + // Update the priority.
1528 + root.remainingExpirationTime = expirationTime;
1529 }
1530 }
1531 +}
1532
1598 - function findHighestPriorityRoot() {
1599 - let highestPriorityWork = NoWork;
1600 - let highestPriorityRoot = null;
1601 - if (lastScheduledRoot !== null) {
1602 - let previousScheduledRoot = lastScheduledRoot;
1603 - let root = firstScheduledRoot;
1604 - while (root !== null) {
1605 - const remainingExpirationTime = root.remainingExpirationTime;
1606 - if (remainingExpirationTime === NoWork) {
1607 - // This root no longer has work. Remove it from the scheduler.
1608 -
1609 - // TODO: This check is redudant, but Flow is confused by the branch
1610 - // below where we set lastScheduledRoot to null, even though we break
1611 - // from the loop right after.
1612 - invariant(
1613 - previousScheduledRoot !== null && lastScheduledRoot !== null,
1614 - 'Should have a previous and last root. This error is likely ' +
1615 - 'caused by a bug in React. Please file an issue.',
1616 - );
1617 - if (root === root.nextScheduledRoot) {
1618 - // This is the only root in the list.
1619 - root.nextScheduledRoot = null;
1620 - firstScheduledRoot = lastScheduledRoot = null;
1621 - break;
1622 - } else if (root === firstScheduledRoot) {
1623 - // This is the first root in the list.
1624 - const next = root.nextScheduledRoot;
1625 - firstScheduledRoot = next;
1626 - lastScheduledRoot.nextScheduledRoot = next;
1627 - root.nextScheduledRoot = null;
1628 - } else if (root === lastScheduledRoot) {
1629 - // This is the last root in the list.
1630 - lastScheduledRoot = previousScheduledRoot;
1631 - lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
1632 - root.nextScheduledRoot = null;
1633 - break;
1634 - } else {
1635 - previousScheduledRoot.nextScheduledRoot = root.nextScheduledRoot;
1636 - root.nextScheduledRoot = null;
1637 - }
1638 - root = previousScheduledRoot.nextScheduledRoot;
1533 +function findHighestPriorityRoot() {
1534 + let highestPriorityWork = NoWork;
1535 + let highestPriorityRoot = null;
1536 + if (lastScheduledRoot !== null) {
1537 + let previousScheduledRoot = lastScheduledRoot;
1538 + let root = firstScheduledRoot;
1539 + while (root !== null) {
1540 + const remainingExpirationTime = root.remainingExpirationTime;
1541 + if (remainingExpirationTime === NoWork) {
1542 + // This root no longer has work. Remove it from the scheduler.
1543 +
1544 + // TODO: This check is redudant, but Flow is confused by the branch
1545 + // below where we set lastScheduledRoot to null, even though we break
1546 + // from the loop right after.
1547 + invariant(
1548 + previousScheduledRoot !== null && lastScheduledRoot !== null,
1549 + 'Should have a previous and last root. This error is likely ' +
1550 + 'caused by a bug in React. Please file an issue.',
1551 + );
1552 + if (root === root.nextScheduledRoot) {
1553 + // This is the only root in the list.
1554 + root.nextScheduledRoot = null;
1555 + firstScheduledRoot = lastScheduledRoot = null;
1556 + break;
1557 + } else if (root === firstScheduledRoot) {
1558 + // This is the first root in the list.
1559 + const next = root.nextScheduledRoot;
1560 + firstScheduledRoot = next;
1561 + lastScheduledRoot.nextScheduledRoot = next;
1562 + root.nextScheduledRoot = null;
1563 + } else if (root === lastScheduledRoot) {
1564 + // This is the last root in the list.
1565 + lastScheduledRoot = previousScheduledRoot;
1566 + lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
1567 + root.nextScheduledRoot = null;
1568 + break;
1569 } else {
1640 - if (
1641 - highestPriorityWork === NoWork ||
1642 - remainingExpirationTime < highestPriorityWork
1643 - ) {
1644 - // Update the priority, if it's higher
1645 - highestPriorityWork = remainingExpirationTime;
1646 - highestPriorityRoot = root;
1647 - }
1648 - if (root === lastScheduledRoot) {
1649 - break;
1650 - }
1651 - previousScheduledRoot = root;
1652 - root = root.nextScheduledRoot;
1570 + previousScheduledRoot.nextScheduledRoot = root.nextScheduledRoot;
1571 + root.nextScheduledRoot = null;
1572 + }
1573 + root = previousScheduledRoot.nextScheduledRoot;
1574 + } else {
1575 + if (
1576 + highestPriorityWork === NoWork ||
1577 + remainingExpirationTime < highestPriorityWork
1578 + ) {
1579 + // Update the priority, if it's higher
1580 + highestPriorityWork = remainingExpirationTime;
1581 + highestPriorityRoot = root;
1582 }
1583 + if (root === lastScheduledRoot) {
1584 + break;
1585 + }
1586 + previousScheduledRoot = root;
1587 + root = root.nextScheduledRoot;
1588 }
1589 }
1656 -
1657 - // If the next root is the same as the previous root, this is a nested
1658 - // update. To prevent an infinite loop, increment the nested update count.
1659 - const previousFlushedRoot = nextFlushedRoot;
1660 - if (
1661 - previousFlushedRoot !== null &&
1662 - previousFlushedRoot === highestPriorityRoot &&
1663 - highestPriorityWork === Sync
1664 - ) {
1665 - nestedUpdateCount++;
1666 - } else {
1667 - // Reset whenever we switch roots.
1668 - nestedUpdateCount = 0;
1669 - }
1670 - nextFlushedRoot = highestPriorityRoot;
1671 - nextFlushedExpirationTime = highestPriorityWork;
1590 }
1591
1674 - function performAsyncWork(dl) {
1675 - performWork(NoWork, true, dl);
1676 - }
1677 -
1678 - function performSyncWork() {
1679 - performWork(Sync, false, null);
1592 + // If the next root is the same as the previous root, this is a nested
1593 + // update. To prevent an infinite loop, increment the nested update count.
1594 + const previousFlushedRoot = nextFlushedRoot;
1595 + if (
1596 + previousFlushedRoot !== null &&
1597 + previousFlushedRoot === highestPriorityRoot &&
1598 + highestPriorityWork === Sync
1599 + ) {
1600 + nestedUpdateCount++;
1601 + } else {
1602 + // Reset whenever we switch roots.
1603 + nestedUpdateCount = 0;
1604 }
1605 + nextFlushedRoot = highestPriorityRoot;
1606 + nextFlushedExpirationTime = highestPriorityWork;
1607 +}
1608
1682 - function performWork(
1683 - minExpirationTime: ExpirationTime,
1684 - isAsync: boolean,
1685 - dl: Deadline | null,
1686 - ) {
1687 - deadline = dl;
1609 +function performAsyncWork(dl) {
1610 + performWork(NoWork, true, dl);
1611 +}
1612
1689 - // Keep working on roots until there's no more work, or until the we reach
1690 - // the deadline.
1691 - findHighestPriorityRoot();
1613 +function performSyncWork() {
1614 + performWork(Sync, false, null);
1615 +}
1616
1693 - if (enableProfilerTimer) {
1694 - resumeActualRenderTimerIfPaused();
1695 - }
1617 +function performWork(
1618 + minExpirationTime: ExpirationTime,
1619 + isAsync: boolean,
1620 + dl: Deadline | null,
1621 +) {
1622 + deadline = dl;
1623
1697 - if (enableUserTimingAPI && deadline !== null) {
1698 - const didExpire = nextFlushedExpirationTime < recalculateCurrentTime();
1699 - const timeout = expirationTimeToMs(nextFlushedExpirationTime);
1700 - stopRequestCallbackTimer(didExpire, timeout);
1701 - }
1624 + // Keep working on roots until there's no more work, or until the we reach
1625 + // the deadline.
1626 + findHighestPriorityRoot();
1627
1703 - if (isAsync) {
1704 - while (
1705 - nextFlushedRoot !== null &&
1706 - nextFlushedExpirationTime !== NoWork &&
1707 - (minExpirationTime === NoWork ||
1708 - minExpirationTime >= nextFlushedExpirationTime) &&
1709 - (!deadlineDidExpire ||
1710 - recalculateCurrentTime() >= nextFlushedExpirationTime)
1711 - ) {
1712 - recalculateCurrentTime();
1713 - performWorkOnRoot(
1714 - nextFlushedRoot,
1715 - nextFlushedExpirationTime,
1716 - !deadlineDidExpire,
1717 - );
1718 - findHighestPriorityRoot();
1719 - }
1720 - } else {
1721 - while (
1722 - nextFlushedRoot !== null &&
1723 - nextFlushedExpirationTime !== NoWork &&
1724 - (minExpirationTime === NoWork ||
1725 - minExpirationTime >= nextFlushedExpirationTime)
1726 - ) {
1727 - performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, false);
1728 - findHighestPriorityRoot();
1729 - }
1730 - }
1628 + if (enableProfilerTimer) {
1629 + resumeActualRenderTimerIfPaused();
1630 + }
1631
1732 - // We're done flushing work. Either we ran out of time in this callback,
1733 - // or there's no more work left with sufficient priority.
1632 + if (enableUserTimingAPI && deadline !== null) {
1633 + const didExpire = nextFlushedExpirationTime < recalculateCurrentTime();
1634 + const timeout = expirationTimeToMs(nextFlushedExpirationTime);
1635 + stopRequestCallbackTimer(didExpire, timeout);
1636 + }
1637
1735 - // If we're inside a callback, set this to false since we just completed it.
1736 - if (deadline !== null) {
1737 - callbackExpirationTime = NoWork;
1738 - callbackID = -1;
1739 - }
1740 - // If there's work left over, schedule a new callback.
1741 - if (nextFlushedExpirationTime !== NoWork) {
1742 - scheduleCallbackWithExpiration(nextFlushedExpirationTime);
1638 + if (isAsync) {
1639 + while (
1640 + nextFlushedRoot !== null &&
1641 + nextFlushedExpirationTime !== NoWork &&
1642 + (minExpirationTime === NoWork ||
1643 + minExpirationTime >= nextFlushedExpirationTime) &&
1644 + (!deadlineDidExpire ||
1645 + recalculateCurrentTime() >= nextFlushedExpirationTime)
1646 + ) {
1647 + recalculateCurrentTime();
1648 + performWorkOnRoot(
1649 + nextFlushedRoot,
1650 + nextFlushedExpirationTime,
1651 + !deadlineDidExpire,
1652 + );
1653 + findHighestPriorityRoot();
1654 + }
1655 + } else {
1656 + while (
1657 + nextFlushedRoot !== null &&
1658 + nextFlushedExpirationTime !== NoWork &&
1659 + (minExpirationTime === NoWork ||
1660 + minExpirationTime >= nextFlushedExpirationTime)
1661 + ) {
1662 + performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, false);
1663 + findHighestPriorityRoot();
1664 }
1665 + }
1666
1745 - // Clean-up.
1746 - deadline = null;
1747 - deadlineDidExpire = false;
1667 + // We're done flushing work. Either we ran out of time in this callback,
1668 + // or there's no more work left with sufficient priority.
1669
1749 - finishRendering();
1670 + // If we're inside a callback, set this to false since we just completed it.
1671 + if (deadline !== null) {
1672 + callbackExpirationTime = NoWork;
1673 + callbackID = -1;
1674 }
1751 -
1752 - function flushRoot(root: FiberRoot, expirationTime: ExpirationTime) {
1753 - invariant(
1754 - !isRendering,
1755 - 'work.commit(): Cannot commit while already rendering. This likely ' +
1756 - 'means you attempted to commit from inside a lifecycle method.',
1757 - );
1758 - // Perform work on root as if the given expiration time is the current time.
1759 - // This has the effect of synchronously flushing all work up to and
1760 - // including the given time.
1761 - nextFlushedRoot = root;
1762 - nextFlushedExpirationTime = expirationTime;
1763 - performWorkOnRoot(root, expirationTime, false);
1764 - // Flush any sync work that was scheduled by lifecycles
1765 - performSyncWork();
1766 - finishRendering();
1675 + // If there's work left over, schedule a new callback.
1676 + if (nextFlushedExpirationTime !== NoWork) {
1677 + scheduleCallbackWithExpiration(nextFlushedExpirationTime);
1678 }
1679
1769 - function finishRendering() {
1770 - nestedUpdateCount = 0;
1680 + // Clean-up.
1681 + deadline = null;
1682 + deadlineDidExpire = false;
1683
1772 - if (completedBatches !== null) {
1773 - const batches = completedBatches;
1774 - completedBatches = null;
1775 - for (let i = 0; i < batches.length; i++) {
1776 - const batch = batches[i];
1777 - try {
1778 - batch._onComplete();
1779 - } catch (error) {
1780 - if (!hasUnhandledError) {
1781 - hasUnhandledError = true;
1782 - unhandledError = error;
1783 - }
1684 + finishRendering();
1685 +}
1686 +
1687 +function flushRoot(root: FiberRoot, expirationTime: ExpirationTime) {
1688 + invariant(
1689 + !isRendering,
1690 + 'work.commit(): Cannot commit while already rendering. This likely ' +
1691 + 'means you attempted to commit from inside a lifecycle method.',
1692 + );
1693 + // Perform work on root as if the given expiration time is the current time.
1694 + // This has the effect of synchronously flushing all work up to and
1695 + // including the given time.
1696 + nextFlushedRoot = root;
1697 + nextFlushedExpirationTime = expirationTime;
1698 + performWorkOnRoot(root, expirationTime, false);
1699 + // Flush any sync work that was scheduled by lifecycles
1700 + performSyncWork();
1701 + finishRendering();
1702 +}
1703 +
1704 +function finishRendering() {
1705 + nestedUpdateCount = 0;
1706 +
1707 + if (completedBatches !== null) {
1708 + const batches = completedBatches;
1709 + completedBatches = null;
1710 + for (let i = 0; i < batches.length; i++) {
1711 + const batch = batches[i];
1712 + try {
1713 + batch._onComplete();
1714 + } catch (error) {
1715 + if (!hasUnhandledError) {
1716 + hasUnhandledError = true;
1717 + unhandledError = error;
1718 }
1719 }
1720 }
1721 + }
1722
1788 - if (hasUnhandledError) {
1789 - const error = unhandledError;
1790 - unhandledError = null;
1791 - hasUnhandledError = false;
1792 - throw error;
1793 - }
1723 + if (hasUnhandledError) {
1724 + const error = unhandledError;
1725 + unhandledError = null;
1726 + hasUnhandledError = false;
1727 + throw error;
1728 }
1729 +}
1730
1796 - function performWorkOnRoot(
1797 - root: FiberRoot,
1798 - expirationTime: ExpirationTime,
1799 - isAsync: boolean,
1800 - ) {
1801 - invariant(
1802 - !isRendering,
1803 - 'performWorkOnRoot was called recursively. This error is likely caused ' +
1804 - 'by a bug in React. Please file an issue.',
1805 - );
1731 +function performWorkOnRoot(
1732 + root: FiberRoot,
1733 + expirationTime: ExpirationTime,
1734 + isAsync: boolean,
1735 +) {
1736 + invariant(
1737 + !isRendering,
1738 + 'performWorkOnRoot was called recursively. This error is likely caused ' +
1739 + 'by a bug in React. Please file an issue.',
1740 + );
1741
1807 - isRendering = true;
1742 + isRendering = true;
1743
1809 - // Check if this is async work or sync/expired work.
1810 - if (!isAsync) {
1811 - // Flush sync work.
1812 - let finishedWork = root.finishedWork;
1744 + // Check if this is async work or sync/expired work.
1745 + if (!isAsync) {
1746 + // Flush sync work.
1747 + let finishedWork = root.finishedWork;
1748 + if (finishedWork !== null) {
1749 + // This root is already complete. We can commit it.
1750 + completeRoot(root, finishedWork, expirationTime);
1751 + } else {
1752 + root.finishedWork = null;
1753 + finishedWork = renderRoot(root, expirationTime, false);
1754 if (finishedWork !== null) {
1814 - // This root is already complete. We can commit it.
1755 + // We've completed the root. Commit it.
1756 completeRoot(root, finishedWork, expirationTime);
1816 - } else {
1817 - root.finishedWork = null;
1818 - finishedWork = renderRoot(root, expirationTime, false);
1819 - if (finishedWork !== null) {
1820 - // We've completed the root. Commit it.
1821 - completeRoot(root, finishedWork, expirationTime);
1822 - }
1757 }
1758 + }
1759 + } else {
1760 + // Flush async work.
1761 + let finishedWork = root.finishedWork;
1762 + if (finishedWork !== null) {
1763 + // This root is already complete. We can commit it.
1764 + completeRoot(root, finishedWork, expirationTime);
1765 } else {
1825 - // Flush async work.
1826 - let finishedWork = root.finishedWork;
1766 + root.finishedWork = null;
1767 + finishedWork = renderRoot(root, expirationTime, true);
1768 if (finishedWork !== null) {
1828 - // This root is already complete. We can commit it.
1829 - completeRoot(root, finishedWork, expirationTime);
1830 - } else {
1831 - root.finishedWork = null;
1832 - finishedWork = renderRoot(root, expirationTime, true);
1833 - if (finishedWork !== null) {
1834 - // We've completed the root. Check the deadline one more time
1835 - // before committing.
1836 - if (!shouldYield()) {
1837 - // Still time left. Commit the root.
1838 - completeRoot(root, finishedWork, expirationTime);
1839 - } else {
1840 - // There's no time left. Mark this root as complete. We'll come
1841 - // back and commit it later.
1842 - root.finishedWork = finishedWork;
1843 -
1844 - if (enableProfilerTimer) {
1845 - // If we didn't finish, pause the "actual" render timer.
1846 - // We'll restart it when we resume work.
1847 - pauseActualRenderTimerIfRunning();
1848 - }
1769 + // We've completed the root. Check the deadline one more time
1770 + // before committing.
1771 + if (!shouldYield()) {
1772 + // Still time left. Commit the root.
1773 + completeRoot(root, finishedWork, expirationTime);
1774 + } else {
1775 + // There's no time left. Mark this root as complete. We'll come
1776 + // back and commit it later.
1777 + root.finishedWork = finishedWork;
1778 +
1779 + if (enableProfilerTimer) {
1780 + // If we didn't finish, pause the "actual" render timer.
1781 + // We'll restart it when we resume work.
1782 + pauseActualRenderTimerIfRunning();
1783 }
1784 }
1785 }
1786 }
1853 -
1854 - isRendering = false;
1787 }
1788
1857 - function completeRoot(
1858 - root: FiberRoot,
1859 - finishedWork: Fiber,
1860 - expirationTime: ExpirationTime,
1861 - ): void {
1862 - // Check if there's a batch that matches this expiration time.
1863 - const firstBatch = root.firstBatch;
1864 - if (firstBatch !== null && firstBatch._expirationTime <= expirationTime) {
1865 - if (completedBatches === null) {
1866 - completedBatches = [firstBatch];
1867 - } else {
1868 - completedBatches.push(firstBatch);
1869 - }
1870 - if (firstBatch._defer) {
1871 - // This root is blocked from committing by a batch. Unschedule it until
1872 - // we receive another update.
1873 - root.finishedWork = finishedWork;
1874 - root.remainingExpirationTime = NoWork;
1875 - return;
1876 - }
1877 - }
1878 -
1879 - // Commit the root.
1880 - root.finishedWork = null;
1881 - root.remainingExpirationTime = commitRoot(finishedWork);
1882 - }
1789 + isRendering = false;
1790 +}
1791
1884 - // When working on async work, the reconciler asks the renderer if it should
1885 - // yield execution. For DOM, we implement this with requestIdleCallback.
1886 - function shouldYield() {
1887 - if (deadline === null) {
1888 - return false;
1792 +function completeRoot(
1793 + root: FiberRoot,
1794 + finishedWork: Fiber,
1795 + expirationTime: ExpirationTime,
1796 +): void {
1797 + // Check if there's a batch that matches this expiration time.
1798 + const firstBatch = root.firstBatch;
1799 + if (firstBatch !== null && firstBatch._expirationTime <= expirationTime) {
1800 + if (completedBatches === null) {
1801 + completedBatches = [firstBatch];
1802 + } else {
1803 + completedBatches.push(firstBatch);
1804 }
1890 - if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
1891 - // Disregard deadline.didTimeout. Only expired work should be flushed
1892 - // during a timeout. This path is only hit for non-expired work.
1893 - return false;
1805 + if (firstBatch._defer) {
1806 + // This root is blocked from committing by a batch. Unschedule it until
1807 + // we receive another update.
1808 + root.finishedWork = finishedWork;
1809 + root.remainingExpirationTime = NoWork;
1810 + return;
1811 }
1895 - deadlineDidExpire = true;
1896 - return true;
1812 }
1813
1899 - function onUncaughtError(error) {
1900 - invariant(
1901 - nextFlushedRoot !== null,
1902 - 'Should be working on a root. This error is likely caused by a bug in ' +
1903 - 'React. Please file an issue.',
1904 - );
1905 - // Unschedule this root so we don't work on it again until there's
1906 - // another update.
1907 - nextFlushedRoot.remainingExpirationTime = NoWork;
1908 - if (!hasUnhandledError) {
1909 - hasUnhandledError = true;
1910 - unhandledError = error;
1911 - }
1912 - }
1814 + // Commit the root.
1815 + root.finishedWork = null;
1816 + root.remainingExpirationTime = commitRoot(finishedWork);
1817 +}
1818
1914 - function onBlock(remainingExpirationTime: ExpirationTime) {
1915 - invariant(
1916 - nextFlushedRoot !== null,
1917 - 'Should be working on a root. This error is likely caused by a bug in ' +
1918 - 'React. Please file an issue.',
1919 - );
1920 - // This root was blocked. Unschedule it until there's another update.
1921 - nextFlushedRoot.remainingExpirationTime = remainingExpirationTime;
1819 +// When working on async work, the reconciler asks the renderer if it should
1820 +// yield execution. For DOM, we implement this with requestIdleCallback.
1821 +function shouldYield() {
1822 + if (deadline === null) {
1823 + return false;
1824 }
1923 -
1924 - // TODO: Batching should be implemented at the renderer level, not inside
1925 - // the reconciler.
1926 - function batchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
1927 - const previousIsBatchingUpdates = isBatchingUpdates;
1928 - isBatchingUpdates = true;
1929 - try {
1930 - return fn(a);
1931 - } finally {
1932 - isBatchingUpdates = previousIsBatchingUpdates;
1933 - if (!isBatchingUpdates && !isRendering) {
1934 - performSyncWork();
1935 - }
1936 - }
1825 + if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
1826 + // Disregard deadline.didTimeout. Only expired work should be flushed
1827 + // during a timeout. This path is only hit for non-expired work.
1828 + return false;
1829 }
1830 + deadlineDidExpire = true;
1831 + return true;
1832 +}
1833
1939 - // TODO: Batching should be implemented at the renderer level, not inside
1940 - // the reconciler.
1941 - function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
1942 - if (isBatchingUpdates && !isUnbatchingUpdates) {
1943 - isUnbatchingUpdates = true;
1944 - try {
1945 - return fn(a);
1946 - } finally {
1947 - isUnbatchingUpdates = false;
1948 - }
1949 - }
1950 - return fn(a);
1834 +function onUncaughtError(error: mixed) {
1835 + invariant(
1836 + nextFlushedRoot !== null,
1837 + 'Should be working on a root. This error is likely caused by a bug in ' +
1838 + 'React. Please file an issue.',
1839 + );
1840 + // Unschedule this root so we don't work on it again until there's
1841 + // another update.
1842 + nextFlushedRoot.remainingExpirationTime = NoWork;
1843 + if (!hasUnhandledError) {
1844 + hasUnhandledError = true;
1845 + unhandledError = error;
1846 }
1847 +}
1848
1953 - // TODO: Batching should be implemented at the renderer level, not within
1954 - // the reconciler.
1955 - function flushSync<A, R>(fn: (a: A) => R, a: A): R {
1956 - invariant(
1957 - !isRendering,
1958 - 'flushSync was called from inside a lifecycle method. It cannot be ' +
1959 - 'called when React is already rendering.',
1960 - );
1961 - const previousIsBatchingUpdates = isBatchingUpdates;
1962 - isBatchingUpdates = true;
1963 - try {
1964 - return syncUpdates(fn, a);
1965 - } finally {
1966 - isBatchingUpdates = previousIsBatchingUpdates;
1849 +function onBlock(remainingExpirationTime: ExpirationTime) {
1850 + invariant(
1851 + nextFlushedRoot !== null,
1852 + 'Should be working on a root. This error is likely caused by a bug in ' +
1853 + 'React. Please file an issue.',
1854 + );
1855 + // This root was blocked. Unschedule it until there's another update.
1856 + nextFlushedRoot.remainingExpirationTime = remainingExpirationTime;
1857 +}
1858 +
1859 +// TODO: Batching should be implemented at the renderer level, not inside
1860 +// the reconciler.
1861 +function batchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
1862 + const previousIsBatchingUpdates = isBatchingUpdates;
1863 + isBatchingUpdates = true;
1864 + try {
1865 + return fn(a);
1866 + } finally {
1867 + isBatchingUpdates = previousIsBatchingUpdates;
1868 + if (!isBatchingUpdates && !isRendering) {
1869 performSyncWork();
1870 }
1871 }
1872 +}
1873
1971 - function interactiveUpdates<A, B, R>(fn: (A, B) => R, a: A, b: B): R {
1972 - if (isBatchingInteractiveUpdates) {
1973 - return fn(a, b);
1974 - }
1975 - // If there are any pending interactive updates, synchronously flush them.
1976 - // This needs to happen before we read any handlers, because the effect of
1977 - // the previous event may influence which handlers are called during
1978 - // this event.
1979 - if (
1980 - !isBatchingUpdates &&
1981 - !isRendering &&
1982 - lowestPendingInteractiveExpirationTime !== NoWork
1983 - ) {
1984 - // Synchronously flush pending interactive updates.
1985 - performWork(lowestPendingInteractiveExpirationTime, false, null);
1986 - lowestPendingInteractiveExpirationTime = NoWork;
1987 - }
1988 - const previousIsBatchingInteractiveUpdates = isBatchingInteractiveUpdates;
1989 - const previousIsBatchingUpdates = isBatchingUpdates;
1990 - isBatchingInteractiveUpdates = true;
1991 - isBatchingUpdates = true;
1874 +// TODO: Batching should be implemented at the renderer level, not inside
1875 +// the reconciler.
1876 +function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
1877 + if (isBatchingUpdates && !isUnbatchingUpdates) {
1878 + isUnbatchingUpdates = true;
1879 try {
1993 - return fn(a, b);
1880 + return fn(a);
1881 } finally {
1995 - isBatchingInteractiveUpdates = previousIsBatchingInteractiveUpdates;
1996 - isBatchingUpdates = previousIsBatchingUpdates;
1997 - if (!isBatchingUpdates && !isRendering) {
1998 - performSyncWork();
1999 - }
1882 + isUnbatchingUpdates = false;
1883 }
1884 }
1885 + return fn(a);
1886 +}
1887
2003 - function flushInteractiveUpdates() {
2004 - if (!isRendering && lowestPendingInteractiveExpirationTime !== NoWork) {
2005 - // Synchronously flush pending interactive updates.
2006 - performWork(lowestPendingInteractiveExpirationTime, false, null);
2007 - lowestPendingInteractiveExpirationTime = NoWork;
2008 - }
1888 +// TODO: Batching should be implemented at the renderer level, not within
1889 +// the reconciler.
1890 +function flushSync<A, R>(fn: (a: A) => R, a: A): R {
1891 + invariant(
1892 + !isRendering,
1893 + 'flushSync was called from inside a lifecycle method. It cannot be ' +
1894 + 'called when React is already rendering.',
1895 + );
1896 + const previousIsBatchingUpdates = isBatchingUpdates;
1897 + isBatchingUpdates = true;
1898 + try {
1899 + return syncUpdates(fn, a);
1900 + } finally {
1901 + isBatchingUpdates = previousIsBatchingUpdates;
1902 + performSyncWork();
1903 }
1904 +}
1905
2011 - function flushControlled(fn: () => mixed): void {
2012 - const previousIsBatchingUpdates = isBatchingUpdates;
2013 - isBatchingUpdates = true;
2014 - try {
2015 - syncUpdates(fn);
2016 - } finally {
2017 - isBatchingUpdates = previousIsBatchingUpdates;
2018 - if (!isBatchingUpdates && !isRendering) {
2019 - performWork(Sync, false, null);
2020 - }
1906 +function interactiveUpdates<A, B, R>(fn: (A, B) => R, a: A, b: B): R {
1907 + if (isBatchingInteractiveUpdates) {
1908 + return fn(a, b);
1909 + }
1910 + // If there are any pending interactive updates, synchronously flush them.
1911 + // This needs to happen before we read any handlers, because the effect of
1912 + // the previous event may influence which handlers are called during
1913 + // this event.
1914 + if (
1915 + !isBatchingUpdates &&
1916 + !isRendering &&
1917 + lowestPendingInteractiveExpirationTime !== NoWork
1918 + ) {
1919 + // Synchronously flush pending interactive updates.
1920 + performWork(lowestPendingInteractiveExpirationTime, false, null);
1921 + lowestPendingInteractiveExpirationTime = NoWork;
1922 + }
1923 + const previousIsBatchingInteractiveUpdates = isBatchingInteractiveUpdates;
1924 + const previousIsBatchingUpdates = isBatchingUpdates;
1925 + isBatchingInteractiveUpdates = true;
1926 + isBatchingUpdates = true;
1927 + try {
1928 + return fn(a, b);
1929 + } finally {
1930 + isBatchingInteractiveUpdates = previousIsBatchingInteractiveUpdates;
1931 + isBatchingUpdates = previousIsBatchingUpdates;
1932 + if (!isBatchingUpdates && !isRendering) {
1933 + performSyncWork();
1934 }
1935 }
1936 +}
1937
2024 - return {
2025 - recalculateCurrentTime,
2026 - computeExpirationForFiber,
2027 - scheduleWork,
2028 - requestWork,
2029 - flushRoot,
2030 - batchedUpdates,
2031 - unbatchedUpdates,
2032 - flushSync,
2033 - flushControlled,
2034 - deferredUpdates,
2035 - syncUpdates,
2036 - interactiveUpdates,
2037 - flushInteractiveUpdates,
2038 - computeUniqueAsyncExpiration,
2039 - legacyContext,
2040 - };
1938 +function flushInteractiveUpdates() {
1939 + if (!isRendering && lowestPendingInteractiveExpirationTime !== NoWork) {
1940 + // Synchronously flush pending interactive updates.
1941 + performWork(lowestPendingInteractiveExpirationTime, false, null);
1942 + lowestPendingInteractiveExpirationTime = NoWork;
1943 + }
1944 }
1945 +
1946 +function flushControlled(fn: () => mixed): void {
1947 + const previousIsBatchingUpdates = isBatchingUpdates;
1948 + isBatchingUpdates = true;
1949 + try {
1950 + syncUpdates(fn);
1951 + } finally {
1952 + isBatchingUpdates = previousIsBatchingUpdates;
1953 + if (!isBatchingUpdates && !isRendering) {
1954 + performWork(Sync, false, null);
1955 + }
1956 + }
1957 +}
1958 +
1959 +export {
1960 + recalculateCurrentTime,
1961 + computeExpirationForFiber,
1962 + captureCommitPhaseError,
1963 + onUncaughtError,
1964 + suspendRoot,
1965 + retrySuspendedRoot,
1966 + markLegacyErrorBoundaryAsFailed,
1967 + isAlreadyFailedLegacyErrorBoundary,
1968 + scheduleWork,
1969 + requestWork,
1970 + flushRoot,
1971 + batchedUpdates,
1972 + unbatchedUpdates,
1973 + flushSync,
1974 + flushControlled,
1975 + deferredUpdates,
1976 + syncUpdates,
1977 + interactiveUpdates,
1978 + flushInteractiveUpdates,
1979 + computeUniqueAsyncExpiration,
1980 +};
packages/react-reconciler/src/ReactFiberStack.js
+61 -73
@@ -15,100 +15,88 @@ export type StackCursor<T> = {
15 current: T,
16 };
17
18 -export type Stack = {
19 - createCursor<T>(defaultValue: T): StackCursor<T>,
20 - isEmpty(): boolean,
21 - push<T>(cursor: StackCursor<T>, value: T, fiber: Fiber): void,
22 - pop<T>(cursor: StackCursor<T>, fiber: Fiber): void,
23 -
24 - // DEV only
25 - checkThatStackIsEmpty(): void,
26 - resetStackAfterFatalErrorInDev(): void,
27 -};
28 -
29 -export default function(): Stack {
30 - const valueStack: Array<any> = [];
31 -
32 - let fiberStack: Array<Fiber | null>;
18 +const valueStack: Array<any> = [];
19
34 - if (__DEV__) {
35 - fiberStack = [];
36 - }
20 +let fiberStack: Array<Fiber | null>;
21
38 - let index = -1;
22 +if (__DEV__) {
23 + fiberStack = [];
24 +}
25
40 - function createCursor<T>(defaultValue: T): StackCursor<T> {
41 - return {
42 - current: defaultValue,
43 - };
44 - }
26 +let index = -1;
27
46 - function isEmpty(): boolean {
47 - return index === -1;
48 - }
28 +function createCursor<T>(defaultValue: T): StackCursor<T> {
29 + return {
30 + current: defaultValue,
31 + };
32 +}
33
50 - function pop<T>(cursor: StackCursor<T>, fiber: Fiber): void {
51 - if (index < 0) {
52 - if (__DEV__) {
53 - warning(false, 'Unexpected pop.');
54 - }
55 - return;
56 - }
34 +function isEmpty(): boolean {
35 + return index === -1;
36 +}
37
38 +function pop<T>(cursor: StackCursor<T>, fiber: Fiber): void {
39 + if (index < 0) {
40 if (__DEV__) {
59 - if (fiber !== fiberStack[index]) {
60 - warning(false, 'Unexpected Fiber popped.');
61 - }
41 + warning(false, 'Unexpected pop.');
42 }
43 + return;
44 + }
45
64 - cursor.current = valueStack[index];
46 + if (__DEV__) {
47 + if (fiber !== fiberStack[index]) {
48 + warning(false, 'Unexpected Fiber popped.');
49 + }
50 + }
51
66 - valueStack[index] = null;
52 + cursor.current = valueStack[index];
53
68 - if (__DEV__) {
69 - fiberStack[index] = null;
70 - }
54 + valueStack[index] = null;
55
72 - index--;
56 + if (__DEV__) {
57 + fiberStack[index] = null;
58 }
59
75 - function push<T>(cursor: StackCursor<T>, value: T, fiber: Fiber): void {
76 - index++;
60 + index--;
61 +}
62
78 - valueStack[index] = cursor.current;
63 +function push<T>(cursor: StackCursor<T>, value: T, fiber: Fiber): void {
64 + index++;
65
80 - if (__DEV__) {
81 - fiberStack[index] = fiber;
82 - }
66 + valueStack[index] = cursor.current;
67
84 - cursor.current = value;
68 + if (__DEV__) {
69 + fiberStack[index] = fiber;
70 }
71
87 - function checkThatStackIsEmpty() {
88 - if (__DEV__) {
89 - if (index !== -1) {
90 - warning(
91 - false,
92 - 'Expected an empty stack. Something was not reset properly.',
93 - );
94 - }
95 - }
96 - }
72 + cursor.current = value;
73 +}
74
98 - function resetStackAfterFatalErrorInDev() {
99 - if (__DEV__) {
100 - index = -1;
101 - valueStack.length = 0;
102 - fiberStack.length = 0;
75 +function checkThatStackIsEmpty() {
76 + if (__DEV__) {
77 + if (index !== -1) {
78 + warning(
79 + false,
80 + 'Expected an empty stack. Something was not reset properly.',
81 + );
82 }
83 }
84 +}
85
106 - return {
107 - createCursor,
108 - isEmpty,
109 - pop,
110 - push,
111 - checkThatStackIsEmpty,
112 - resetStackAfterFatalErrorInDev,
113 - };
86 +function resetStackAfterFatalErrorInDev() {
87 + if (__DEV__) {
88 + index = -1;
89 + valueStack.length = 0;
90 + fiberStack.length = 0;
91 + }
92 }
93 +
94 +export {
95 + createCursor,
96 + isEmpty,
97 + pop,
98 + push,
99 + // DEV only:
100 + checkThatStackIsEmpty,
101 + resetStackAfterFatalErrorInDev,
102 +};
packages/react-reconciler/src/ReactFiberUnwindWork.js
+306 -330
@@ -7,27 +7,13 @@
7 * @flow
8 */
9
10 -import type {HostConfig} from 'react-reconciler';
10 import type {Fiber} from './ReactFiber';
11 import type {FiberRoot} from './ReactFiberRoot';
12 import type {ExpirationTime} from './ReactFiberExpirationTime';
14 -import type {HostContext} from './ReactFiberHostContext';
15 -import type {LegacyContext} from './ReactFiberContext';
16 -import type {NewContext} from './ReactFiberNewContext';
13 import type {CapturedValue} from './ReactCapturedValue';
18 -import type {ProfilerTimer} from './ReactProfilerTimer';
14 import type {Update} from './ReactUpdateQueue';
15 import type {Thenable} from './ReactFiberScheduler';
16
22 -import {createCapturedValue} from './ReactCapturedValue';
23 -import {
24 - enqueueCapturedUpdate,
25 - createUpdate,
26 - enqueueUpdate,
27 - CaptureUpdate,
28 -} from './ReactUpdateQueue';
29 -import {logError} from './ReactFiberCommitWork';
30 -
17 import {
18 ClassComponent,
19 HostRoot,
@@ -49,238 +35,252 @@ import {
35 enableSuspense,
36 } from 'shared/ReactFeatureFlags';
37
38 +import {createCapturedValue} from './ReactCapturedValue';
39 +import {
40 + enqueueCapturedUpdate,
41 + createUpdate,
42 + enqueueUpdate,
43 + CaptureUpdate,
44 +} from './ReactUpdateQueue';
45 +import {logError} from './ReactFiberCommitWork';
46 import {Never, Sync, expirationTimeToMs} from './ReactFiberExpirationTime';
47 +import {popHostContainer, popHostContext} from './ReactFiberHostContext';
48 +import {
49 + popContextProvider as popLegacyContextProvider,
50 + popTopLevelContextObject as popTopLevelLegacyContextObject,
51 +} from './ReactFiberContext';
52 +import {popProvider} from './ReactFiberNewContext';
53 +import {
54 + resumeActualRenderTimerIfPaused,
55 + recordElapsedActualRenderTime,
56 +} from './ReactProfilerTimer';
57 +import {
58 + suspendRoot,
59 + onUncaughtError,
60 + markLegacyErrorBoundaryAsFailed,
61 + isAlreadyFailedLegacyErrorBoundary,
62 + recalculateCurrentTime,
63 + computeExpirationForFiber,
64 + scheduleWork,
65 + retrySuspendedRoot,
66 +} from './ReactFiberScheduler';
67
54 -export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
55 - config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
56 - hostContext: HostContext<C, CX>,
57 - legacyContext: LegacyContext,
58 - newContext: NewContext,
59 - scheduleWork: (fiber: Fiber, expirationTime: ExpirationTime) => void,
60 - computeExpirationForFiber: (
61 - startTime: ExpirationTime,
62 - fiber: Fiber,
63 - ) => ExpirationTime,
64 - recalculateCurrentTime: () => ExpirationTime,
65 - markLegacyErrorBoundaryAsFailed: (instance: mixed) => void,
66 - isAlreadyFailedLegacyErrorBoundary: (instance: mixed) => boolean,
67 - onUncaughtError: (error: mixed) => void,
68 - profilerTimer: ProfilerTimer,
69 - suspendRoot: (
70 - root: FiberRoot,
71 - thenable: Thenable,
72 - timeoutMs: number,
73 - suspendedTime: ExpirationTime,
74 - ) => void,
75 - retrySuspendedRoot: (root: FiberRoot, suspendedTime: ExpirationTime) => void,
76 -) {
77 - const {popHostContainer, popHostContext} = hostContext;
78 - const {
79 - popContextProvider: popLegacyContextProvider,
80 - popTopLevelContextObject: popTopLevelLegacyContextObject,
81 - } = legacyContext;
82 - const {popProvider} = newContext;
83 - const {
84 - resumeActualRenderTimerIfPaused,
85 - recordElapsedActualRenderTime,
86 - } = profilerTimer;
68 +function createRootErrorUpdate(
69 + fiber: Fiber,
70 + errorInfo: CapturedValue<mixed>,
71 + expirationTime: ExpirationTime,
72 +): Update<null> {
73 + const update = createUpdate(expirationTime);
74 + // Unmount the root by rendering null.
75 + update.tag = CaptureUpdate;
76 + // Caution: React DevTools currently depends on this property
77 + // being called "element".
78 + update.payload = {element: null};
79 + const error = errorInfo.value;
80 + update.callback = () => {
81 + onUncaughtError(error);
82 + logError(fiber, errorInfo);
83 + };
84 + return update;
85 +}
86
88 - function createRootErrorUpdate(
89 - fiber: Fiber,
90 - errorInfo: CapturedValue<mixed>,
91 - expirationTime: ExpirationTime,
92 - ): Update<null> {
93 - const update = createUpdate(expirationTime);
94 - // Unmount the root by rendering null.
95 - update.tag = CaptureUpdate;
96 - // Caution: React DevTools currently depends on this property
97 - // being called "element".
98 - update.payload = {element: null};
87 +function createClassErrorUpdate(
88 + fiber: Fiber,
89 + errorInfo: CapturedValue<mixed>,
90 + expirationTime: ExpirationTime,
91 +): Update<mixed> {
92 + const update = createUpdate(expirationTime);
93 + update.tag = CaptureUpdate;
94 + const getDerivedStateFromCatch = fiber.type.getDerivedStateFromCatch;
95 + if (
96 + enableGetDerivedStateFromCatch &&
97 + typeof getDerivedStateFromCatch === 'function'
98 + ) {
99 const error = errorInfo.value;
100 - update.callback = () => {
101 - onUncaughtError(error);
102 - logError(fiber, errorInfo);
100 + update.payload = () => {
101 + return getDerivedStateFromCatch(error);
102 };
104 - return update;
103 }
104
107 - function createClassErrorUpdate(
108 - fiber: Fiber,
109 - errorInfo: CapturedValue<mixed>,
110 - expirationTime: ExpirationTime,
111 - ): Update<mixed> {
112 - const update = createUpdate(expirationTime);
113 - update.tag = CaptureUpdate;
114 - const getDerivedStateFromCatch = fiber.type.getDerivedStateFromCatch;
115 - if (
116 - enableGetDerivedStateFromCatch &&
117 - typeof getDerivedStateFromCatch === 'function'
118 - ) {
105 + const inst = fiber.stateNode;
106 + if (inst !== null && typeof inst.componentDidCatch === 'function') {
107 + update.callback = function callback() {
108 + if (
109 + !enableGetDerivedStateFromCatch ||
110 + getDerivedStateFromCatch !== 'function'
111 + ) {
112 + // To preserve the preexisting retry behavior of error boundaries,
113 + // we keep track of which ones already failed during this batch.
114 + // This gets reset before we yield back to the browser.
115 + // TODO: Warn in strict mode if getDerivedStateFromCatch is
116 + // not defined.
117 + markLegacyErrorBoundaryAsFailed(this);
118 + }
119 const error = errorInfo.value;
120 - update.payload = () => {
121 - return getDerivedStateFromCatch(error);
122 - };
123 - }
124 -
125 - const inst = fiber.stateNode;
126 - if (inst !== null && typeof inst.componentDidCatch === 'function') {
127 - update.callback = function callback() {
128 - if (
129 - !enableGetDerivedStateFromCatch ||
130 - getDerivedStateFromCatch !== 'function'
131 - ) {
132 - // To preserve the preexisting retry behavior of error boundaries,
133 - // we keep track of which ones already failed during this batch.
134 - // This gets reset before we yield back to the browser.
135 - // TODO: Warn in strict mode if getDerivedStateFromCatch is
136 - // not defined.
137 - markLegacyErrorBoundaryAsFailed(this);
138 - }
139 - const error = errorInfo.value;
140 - const stack = errorInfo.stack;
141 - logError(fiber, errorInfo);
142 - this.componentDidCatch(error, {
143 - componentStack: stack !== null ? stack : '',
144 - });
145 - };
146 - }
147 - return update;
120 + const stack = errorInfo.stack;
121 + logError(fiber, errorInfo);
122 + this.componentDidCatch(error, {
123 + componentStack: stack !== null ? stack : '',
124 + });
125 + };
126 }
127 + return update;
128 +}
129
150 - function schedulePing(finishedWork) {
151 - // Once the promise resolves, we should try rendering the non-
152 - // placeholder state again.
153 - const currentTime = recalculateCurrentTime();
154 - const expirationTime = computeExpirationForFiber(currentTime, finishedWork);
155 - const recoveryUpdate = createUpdate(expirationTime);
156 - enqueueUpdate(finishedWork, recoveryUpdate, expirationTime);
157 - scheduleWork(finishedWork, expirationTime);
158 - }
130 +function schedulePing(finishedWork) {
131 + // Once the promise resolves, we should try rendering the non-
132 + // placeholder state again.
133 + const currentTime = recalculateCurrentTime();
134 + const expirationTime = computeExpirationForFiber(currentTime, finishedWork);
135 + const recoveryUpdate = createUpdate(expirationTime);
136 + enqueueUpdate(finishedWork, recoveryUpdate, expirationTime);
137 + scheduleWork(finishedWork, expirationTime);
138 +}
139
160 - function throwException(
161 - root: FiberRoot,
162 - returnFiber: Fiber,
163 - sourceFiber: Fiber,
164 - value: mixed,
165 - renderIsExpired: boolean,
166 - renderExpirationTime: ExpirationTime,
167 - currentTimeMs: number,
168 - ) {
169 - // The source fiber did not complete.
170 - sourceFiber.effectTag |= Incomplete;
171 - // Its effect list is no longer valid.
172 - sourceFiber.firstEffect = sourceFiber.lastEffect = null;
140 +function throwException(
141 + root: FiberRoot,
142 + returnFiber: Fiber,
143 + sourceFiber: Fiber,
144 + value: mixed,
145 + renderIsExpired: boolean,
146 + renderExpirationTime: ExpirationTime,
147 + currentTimeMs: number,
148 +) {
149 + // The source fiber did not complete.
150 + sourceFiber.effectTag |= Incomplete;
151 + // Its effect list is no longer valid.
152 + sourceFiber.firstEffect = sourceFiber.lastEffect = null;
153
174 - if (
175 - enableSuspense &&
176 - value !== null &&
177 - typeof value === 'object' &&
178 - typeof value.then === 'function'
179 - ) {
180 - // This is a thenable.
181 - const thenable: Thenable = (value: any);
154 + if (
155 + enableSuspense &&
156 + value !== null &&
157 + typeof value === 'object' &&
158 + typeof value.then === 'function'
159 + ) {
160 + // This is a thenable.
161 + const thenable: Thenable = (value: any);
162
183 - const expirationTimeMs = expirationTimeToMs(renderExpirationTime);
184 - const startTimeMs = expirationTimeMs - 5000;
185 - let elapsedMs = currentTimeMs - startTimeMs;
186 - if (elapsedMs < 0) {
187 - elapsedMs = 0;
188 - }
189 - const remainingTimeMs = expirationTimeMs - currentTimeMs;
163 + const expirationTimeMs = expirationTimeToMs(renderExpirationTime);
164 + const startTimeMs = expirationTimeMs - 5000;
165 + let elapsedMs = currentTimeMs - startTimeMs;
166 + if (elapsedMs < 0) {
167 + elapsedMs = 0;
168 + }
169 + const remainingTimeMs = expirationTimeMs - currentTimeMs;
170
191 - // Find the earliest timeout of all the timeouts in the ancestor path.
192 - // TODO: Alternatively, we could store the earliest timeout on the context
193 - // stack, rather than searching on every suspend.
194 - let workInProgress = returnFiber;
195 - let earliestTimeoutMs = -1;
196 - searchForEarliestTimeout: do {
197 - if (workInProgress.tag === TimeoutComponent) {
198 - const current = workInProgress.alternate;
199 - if (current !== null && current.memoizedState === true) {
200 - // A parent Timeout already committed in a placeholder state. We
201 - // need to handle this promise immediately. In other words, we
202 - // should never suspend inside a tree that already expired.
171 + // Find the earliest timeout of all the timeouts in the ancestor path.
172 + // TODO: Alternatively, we could store the earliest timeout on the context
173 + // stack, rather than searching on every suspend.
174 + let workInProgress = returnFiber;
175 + let earliestTimeoutMs = -1;
176 + searchForEarliestTimeout: do {
177 + if (workInProgress.tag === TimeoutComponent) {
178 + const current = workInProgress.alternate;
179 + if (current !== null && current.memoizedState === true) {
180 + // A parent Timeout already committed in a placeholder state. We
181 + // need to handle this promise immediately. In other words, we
182 + // should never suspend inside a tree that already expired.
183 + earliestTimeoutMs = 0;
184 + break searchForEarliestTimeout;
185 + }
186 + let timeoutPropMs = workInProgress.pendingProps.ms;
187 + if (typeof timeoutPropMs === 'number') {
188 + if (timeoutPropMs <= 0) {
189 earliestTimeoutMs = 0;
190 break searchForEarliestTimeout;
191 + } else if (
192 + earliestTimeoutMs === -1 ||
193 + timeoutPropMs < earliestTimeoutMs
194 + ) {
195 + earliestTimeoutMs = timeoutPropMs;
196 + }
197 + } else if (earliestTimeoutMs === -1) {
198 + earliestTimeoutMs = remainingTimeMs;
199 + }
200 + }
201 + workInProgress = workInProgress.return;
202 + } while (workInProgress !== null);
203 +
204 + // Compute the remaining time until the timeout.
205 + const msUntilTimeout = earliestTimeoutMs - elapsedMs;
206 +
207 + if (renderExpirationTime === Never || msUntilTimeout > 0) {
208 + // There's still time remaining.
209 + suspendRoot(root, thenable, msUntilTimeout, renderExpirationTime);
210 + const onResolveOrReject = () => {
211 + retrySuspendedRoot(root, renderExpirationTime);
212 + };
213 + thenable.then(onResolveOrReject, onResolveOrReject);
214 + return;
215 + } else {
216 + // No time remaining. Need to fallback to placeholder.
217 + // Find the nearest timeout that can be retried.
218 + workInProgress = returnFiber;
219 + do {
220 + switch (workInProgress.tag) {
221 + case HostRoot: {
222 + // The root expired, but no fallback was provided. Throw a
223 + // helpful error.
224 + const message =
225 + renderExpirationTime === Sync
226 + ? 'A synchronous update was suspended, but no fallback UI ' +
227 + 'was provided.'
228 + : 'An update was suspended for longer than the timeout, ' +
229 + 'but no fallback UI was provided.';
230 + value = new Error(message);
231 + break;
232 }
206 - let timeoutPropMs = workInProgress.pendingProps.ms;
207 - if (typeof timeoutPropMs === 'number') {
208 - if (timeoutPropMs <= 0) {
209 - earliestTimeoutMs = 0;
210 - break searchForEarliestTimeout;
211 - } else if (
212 - earliestTimeoutMs === -1 ||
213 - timeoutPropMs < earliestTimeoutMs
214 - ) {
215 - earliestTimeoutMs = timeoutPropMs;
233 + case TimeoutComponent: {
234 + if ((workInProgress.effectTag & DidCapture) === NoEffect) {
235 + workInProgress.effectTag |= ShouldCapture;
236 + const onResolveOrReject = schedulePing.bind(null, workInProgress);
237 + thenable.then(onResolveOrReject, onResolveOrReject);
238 + return;
239 }
217 - } else if (earliestTimeoutMs === -1) {
218 - earliestTimeoutMs = remainingTimeMs;
240 + // Already captured during this render. Continue to the next
241 + // Timeout ancestor.
242 + break;
243 }
244 }
245 workInProgress = workInProgress.return;
246 } while (workInProgress !== null);
247 + }
248 + }
249
224 - // Compute the remaining time until the timeout.
225 - const msUntilTimeout = earliestTimeoutMs - elapsedMs;
226 -
227 - if (renderExpirationTime === Never || msUntilTimeout > 0) {
228 - // There's still time remaining.
229 - suspendRoot(root, thenable, msUntilTimeout, renderExpirationTime);
230 - const onResolveOrReject = () => {
231 - retrySuspendedRoot(root, renderExpirationTime);
232 - };
233 - thenable.then(onResolveOrReject, onResolveOrReject);
250 + // We didn't find a boundary that could handle this type of exception. Start
251 + // over and traverse parent path again, this time treating the exception
252 + // as an error.
253 + value = createCapturedValue(value, sourceFiber);
254 + let workInProgress = returnFiber;
255 + do {
256 + switch (workInProgress.tag) {
257 + case HostRoot: {
258 + const errorInfo = value;
259 + workInProgress.effectTag |= ShouldCapture;
260 + const update = createRootErrorUpdate(
261 + workInProgress,
262 + errorInfo,
263 + renderExpirationTime,
264 + );
265 + enqueueCapturedUpdate(workInProgress, update, renderExpirationTime);
266 return;
235 - } else {
236 - // No time remaining. Need to fallback to placeholder.
237 - // Find the nearest timeout that can be retried.
238 - workInProgress = returnFiber;
239 - do {
240 - switch (workInProgress.tag) {
241 - case HostRoot: {
242 - // The root expired, but no fallback was provided. Throw a
243 - // helpful error.
244 - const message =
245 - renderExpirationTime === Sync
246 - ? 'A synchronous update was suspended, but no fallback UI ' +
247 - 'was provided.'
248 - : 'An update was suspended for longer than the timeout, ' +
249 - 'but no fallback UI was provided.';
250 - value = new Error(message);
251 - break;
252 - }
253 - case TimeoutComponent: {
254 - if ((workInProgress.effectTag & DidCapture) === NoEffect) {
255 - workInProgress.effectTag |= ShouldCapture;
256 - const onResolveOrReject = schedulePing.bind(
257 - null,
258 - workInProgress,
259 - );
260 - thenable.then(onResolveOrReject, onResolveOrReject);
261 - return;
262 - }
263 - // Already captured during this render. Continue to the next
264 - // Timeout ancestor.
265 - break;
266 - }
267 - }
268 - workInProgress = workInProgress.return;
269 - } while (workInProgress !== null);
267 }
271 - }
272 -
273 - // We didn't find a boundary that could handle this type of exception. Start
274 - // over and traverse parent path again, this time treating the exception
275 - // as an error.
276 - value = createCapturedValue(value, sourceFiber);
277 - let workInProgress = returnFiber;
278 - do {
279 - switch (workInProgress.tag) {
280 - case HostRoot: {
281 - const errorInfo = value;
268 + case ClassComponent:
269 + // Capture and retry
270 + const errorInfo = value;
271 + const ctor = workInProgress.type;
272 + const instance = workInProgress.stateNode;
273 + if (
274 + (workInProgress.effectTag & DidCapture) === NoEffect &&
275 + ((typeof ctor.getDerivedStateFromCatch === 'function' &&
276 + enableGetDerivedStateFromCatch) ||
277 + (instance !== null &&
278 + typeof instance.componentDidCatch === 'function' &&
279 + !isAlreadyFailedLegacyErrorBoundary(instance)))
280 + ) {
281 workInProgress.effectTag |= ShouldCapture;
283 - const update = createRootErrorUpdate(
282 + // Schedule the error boundary to re-render using updated state
283 + const update = createClassErrorUpdate(
284 workInProgress,
285 errorInfo,
286 renderExpirationTime,
@@ -288,123 +288,99 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
288 enqueueCapturedUpdate(workInProgress, update, renderExpirationTime);
289 return;
290 }
291 - case ClassComponent:
292 - // Capture and retry
293 - const errorInfo = value;
294 - const ctor = workInProgress.type;
295 - const instance = workInProgress.stateNode;
296 - if (
297 - (workInProgress.effectTag & DidCapture) === NoEffect &&
298 - ((typeof ctor.getDerivedStateFromCatch === 'function' &&
299 - enableGetDerivedStateFromCatch) ||
300 - (instance !== null &&
301 - typeof instance.componentDidCatch === 'function' &&
302 - !isAlreadyFailedLegacyErrorBoundary(instance)))
303 - ) {
304 - workInProgress.effectTag |= ShouldCapture;
305 - // Schedule the error boundary to re-render using updated state
306 - const update = createClassErrorUpdate(
307 - workInProgress,
308 - errorInfo,
309 - renderExpirationTime,
310 - );
311 - enqueueCapturedUpdate(workInProgress, update, renderExpirationTime);
312 - return;
313 - }
314 - break;
315 - default:
316 - break;
317 - }
318 - workInProgress = workInProgress.return;
319 - } while (workInProgress !== null);
320 - }
321 -
322 - function unwindWork(
323 - workInProgress: Fiber,
324 - renderIsExpired: boolean,
325 - renderExpirationTime: ExpirationTime,
326 - ) {
327 - switch (workInProgress.tag) {
328 - case ClassComponent: {
329 - popLegacyContextProvider(workInProgress);
330 - const effectTag = workInProgress.effectTag;
331 - if (effectTag & ShouldCapture) {
332 - workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
333 - return workInProgress;
334 - }
335 - return null;
336 - }
337 - case HostRoot: {
338 - popHostContainer(workInProgress);
339 - popTopLevelLegacyContextObject(workInProgress);
340 - const effectTag = workInProgress.effectTag;
341 - if (effectTag & ShouldCapture) {
342 - workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
343 - return workInProgress;
344 - }
345 - return null;
346 - }
347 - case HostComponent: {
348 - popHostContext(workInProgress);
349 - return null;
350 - }
351 - case TimeoutComponent: {
352 - const effectTag = workInProgress.effectTag;
353 - if (effectTag & ShouldCapture) {
354 - workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
355 - return workInProgress;
356 - }
357 - return null;
358 - }
359 - case HostPortal:
360 - popHostContainer(workInProgress);
361 - return null;
362 - case ContextProvider:
363 - popProvider(workInProgress);
364 - return null;
291 + break;
292 default:
366 - return null;
293 + break;
294 }
368 - }
295 + workInProgress = workInProgress.return;
296 + } while (workInProgress !== null);
297 +}
298
370 - function unwindInterruptedWork(interruptedWork: Fiber) {
371 - switch (interruptedWork.tag) {
372 - case ClassComponent: {
373 - popLegacyContextProvider(interruptedWork);
374 - break;
299 +function unwindWork(
300 + workInProgress: Fiber,
301 + renderIsExpired: boolean,
302 + renderExpirationTime: ExpirationTime,
303 +) {
304 + switch (workInProgress.tag) {
305 + case ClassComponent: {
306 + popLegacyContextProvider(workInProgress);
307 + const effectTag = workInProgress.effectTag;
308 + if (effectTag & ShouldCapture) {
309 + workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
310 + return workInProgress;
311 }
376 - case HostRoot: {
377 - popHostContainer(interruptedWork);
378 - popTopLevelLegacyContextObject(interruptedWork);
379 - break;
312 + return null;
313 + }
314 + case HostRoot: {
315 + popHostContainer(workInProgress);
316 + popTopLevelLegacyContextObject(workInProgress);
317 + const effectTag = workInProgress.effectTag;
318 + if (effectTag & ShouldCapture) {
319 + workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
320 + return workInProgress;
321 }
381 - case HostComponent: {
382 - popHostContext(interruptedWork);
383 - break;
322 + return null;
323 + }
324 + case HostComponent: {
325 + popHostContext(workInProgress);
326 + return null;
327 + }
328 + case TimeoutComponent: {
329 + const effectTag = workInProgress.effectTag;
330 + if (effectTag & ShouldCapture) {
331 + workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
332 + return workInProgress;
333 }
385 - case HostPortal:
386 - popHostContainer(interruptedWork);
387 - break;
388 - case ContextProvider:
389 - popProvider(interruptedWork);
390 - break;
391 - case Profiler:
392 - if (enableProfilerTimer) {
393 - // Resume in case we're picking up on work that was paused.
394 - resumeActualRenderTimerIfPaused();
395 - recordElapsedActualRenderTime(interruptedWork);
396 - }
397 - break;
398 - default:
399 - break;
334 + return null;
335 }
336 + case HostPortal:
337 + popHostContainer(workInProgress);
338 + return null;
339 + case ContextProvider:
340 + popProvider(workInProgress);
341 + return null;
342 + default:
343 + return null;
344 }
345 +}
346
403 - return {
404 - throwException,
405 - unwindWork,
406 - unwindInterruptedWork,
407 - createRootErrorUpdate,
408 - createClassErrorUpdate,
409 - };
347 +function unwindInterruptedWork(interruptedWork: Fiber) {
348 + switch (interruptedWork.tag) {
349 + case ClassComponent: {
350 + popLegacyContextProvider(interruptedWork);
351 + break;
352 + }
353 + case HostRoot: {
354 + popHostContainer(interruptedWork);
355 + popTopLevelLegacyContextObject(interruptedWork);
356 + break;
357 + }
358 + case HostComponent: {
359 + popHostContext(interruptedWork);
360 + break;
361 + }
362 + case HostPortal:
363 + popHostContainer(interruptedWork);
364 + break;
365 + case ContextProvider:
366 + popProvider(interruptedWork);
367 + break;
368 + case Profiler:
369 + if (enableProfilerTimer) {
370 + // Resume in case we're picking up on work that was paused.
371 + resumeActualRenderTimerIfPaused();
372 + recordElapsedActualRenderTime(interruptedWork);
373 + }
374 + break;
375 + default:
376 + break;
377 + }
378 }
379 +
380 +export {
381 + throwException,
382 + unwindWork,
383 + unwindInterruptedWork,
384 + createRootErrorUpdate,
385 + createClassErrorUpdate,
386 +};
packages/react-reconciler/src/ReactProfilerTimer.js
+99 -87
@@ -12,6 +12,7 @@ import type {Fiber} from './ReactFiber';
12 import {enableProfilerTimer} from 'shared/ReactFeatureFlags';
13
14 import warning from 'fbjs/lib/warning';
15 +import {now} from './ReactFiberHostConfig';
16
17 /**
18 * The "actual" render time is total time required to render the descendants of a Profiler component.
@@ -32,113 +33,124 @@ export type ProfilerTimer = {
33 stopBaseRenderTimerIfRunning(): void,
34 };
35
35 -export function createProfilerTimer(now: () => number): ProfilerTimer {
36 - let fiberStack: Array<Fiber | null>;
36 +let fiberStack: Array<Fiber | null>;
37
38 - if (__DEV__) {
39 - fiberStack = [];
40 - }
38 +if (__DEV__) {
39 + fiberStack = [];
40 +}
41
42 - let timerPausedAt: number = 0;
43 - let totalElapsedPauseTime: number = 0;
42 +let timerPausedAt: number = 0;
43 +let totalElapsedPauseTime: number = 0;
44
45 - function checkActualRenderTimeStackEmpty(): void {
46 - if (__DEV__) {
47 - warning(
48 - fiberStack.length === 0,
49 - 'Expected an empty stack. Something was not reset properly.',
50 - );
51 - }
45 +function checkActualRenderTimeStackEmpty(): void {
46 + if (!enableProfilerTimer) {
47 + return;
48 + }
49 + if (__DEV__) {
50 + warning(
51 + fiberStack.length === 0,
52 + 'Expected an empty stack. Something was not reset properly.',
53 + );
54 }
55 +}
56
54 - function markActualRenderTimeStarted(fiber: Fiber): void {
55 - if (__DEV__) {
56 - fiberStack.push(fiber);
57 - }
58 - fiber.stateNode.startTime = now() - totalElapsedPauseTime;
57 +function markActualRenderTimeStarted(fiber: Fiber): void {
58 + if (!enableProfilerTimer) {
59 + return;
60 }
61 + if (__DEV__) {
62 + fiberStack.push(fiber);
63 + }
64 + fiber.stateNode.startTime = now() - totalElapsedPauseTime;
65 +}
66
61 - function pauseActualRenderTimerIfRunning(): void {
62 - if (timerPausedAt === 0) {
63 - timerPausedAt = now();
64 - }
67 +function pauseActualRenderTimerIfRunning(): void {
68 + if (!enableProfilerTimer) {
69 + return;
70 + }
71 + if (timerPausedAt === 0) {
72 + timerPausedAt = now();
73 }
74 +}
75
67 - function recordElapsedActualRenderTime(fiber: Fiber): void {
68 - if (__DEV__) {
69 - warning(fiber === fiberStack.pop(), 'Unexpected Fiber popped.');
70 - }
71 - fiber.stateNode.duration +=
72 - now() - totalElapsedPauseTime - fiber.stateNode.startTime;
76 +function recordElapsedActualRenderTime(fiber: Fiber): void {
77 + if (!enableProfilerTimer) {
78 + return;
79 + }
80 + if (__DEV__) {
81 + warning(fiber === fiberStack.pop(), 'Unexpected Fiber popped.');
82 }
83 + fiber.stateNode.duration +=
84 + now() - totalElapsedPauseTime - fiber.stateNode.startTime;
85 +}
86
75 - function resetActualRenderTimer(): void {
76 - totalElapsedPauseTime = 0;
87 +function resetActualRenderTimer(): void {
88 + if (!enableProfilerTimer) {
89 + return;
90 }
91 + totalElapsedPauseTime = 0;
92 +}
93
79 - function resumeActualRenderTimerIfPaused(): void {
80 - if (timerPausedAt > 0) {
81 - totalElapsedPauseTime += now() - timerPausedAt;
82 - timerPausedAt = 0;
83 - }
94 +function resumeActualRenderTimerIfPaused(): void {
95 + if (!enableProfilerTimer) {
96 + return;
97 }
98 + if (timerPausedAt > 0) {
99 + totalElapsedPauseTime += now() - timerPausedAt;
100 + timerPausedAt = 0;
101 + }
102 +}
103
86 - /**
87 - * The "base" render time is the duration of the “begin” phase of work for a particular fiber.
88 - * This time is measured and stored on each fiber.
89 - * The time for all sibling fibers are accumulated and stored on their parent during the "complete" phase.
90 - * If a fiber bails out (sCU false) then its "base" timer is cancelled and the fiber is not updated.
91 - */
104 +/**
105 + * The "base" render time is the duration of the “begin” phase of work for a particular fiber.
106 + * This time is measured and stored on each fiber.
107 + * The time for all sibling fibers are accumulated and stored on their parent during the "complete" phase.
108 + * If a fiber bails out (sCU false) then its "base" timer is cancelled and the fiber is not updated.
109 + */
110
93 - let baseStartTime: number = -1;
111 +let baseStartTime: number = -1;
112
95 - function recordElapsedBaseRenderTimeIfRunning(fiber: Fiber): void {
113 +function recordElapsedBaseRenderTimeIfRunning(fiber: Fiber): void {
114 + if (!enableProfilerTimer) {
115 + return;
116 + }
117 + if (baseStartTime !== -1) {
118 + fiber.selfBaseTime = now() - baseStartTime;
119 + }
120 +}
121 +
122 +function startBaseRenderTimer(): void {
123 + if (!enableProfilerTimer) {
124 + return;
125 + }
126 + if (__DEV__) {
127 if (baseStartTime !== -1) {
97 - fiber.selfBaseTime = now() - baseStartTime;
128 + warning(
129 + false,
130 + 'Cannot start base timer that is already running. ' +
131 + 'This error is likely caused by a bug in React. ' +
132 + 'Please file an issue.',
133 + );
134 }
135 }
136 + baseStartTime = now();
137 +}
138
101 - function startBaseRenderTimer(): void {
102 - if (__DEV__) {
103 - if (baseStartTime !== -1) {
104 - warning(
105 - false,
106 - 'Cannot start base timer that is already running. ' +
107 - 'This error is likely caused by a bug in React. ' +
108 - 'Please file an issue.',
109 - );
110 - }
111 - }
112 - baseStartTime = now();
113 - }
114 -
115 - function stopBaseRenderTimerIfRunning(): void {
116 - baseStartTime = -1;
117 - }
118 -
119 - if (enableProfilerTimer) {
120 - return {
121 - checkActualRenderTimeStackEmpty,
122 - markActualRenderTimeStarted,
123 - pauseActualRenderTimerIfRunning,
124 - recordElapsedActualRenderTime,
125 - resetActualRenderTimer,
126 - resumeActualRenderTimerIfPaused,
127 - recordElapsedBaseRenderTimeIfRunning,
128 - startBaseRenderTimer,
129 - stopBaseRenderTimerIfRunning,
130 - };
131 - } else {
132 - return {
133 - checkActualRenderTimeStackEmpty(): void {},
134 - markActualRenderTimeStarted(fiber: Fiber): void {},
135 - pauseActualRenderTimerIfRunning(): void {},
136 - recordElapsedActualRenderTime(fiber: Fiber): void {},
137 - resetActualRenderTimer(): void {},
138 - resumeActualRenderTimerIfPaused(): void {},
139 - recordElapsedBaseRenderTimeIfRunning(fiber: Fiber): void {},
140 - startBaseRenderTimer(): void {},
141 - stopBaseRenderTimerIfRunning(): void {},
142 - };
139 +function stopBaseRenderTimerIfRunning(): void {
140 + if (!enableProfilerTimer) {
141 + return;
142 }
143 + baseStartTime = -1;
144 }
145 +
146 +export {
147 + checkActualRenderTimeStackEmpty,
148 + markActualRenderTimeStarted,
149 + pauseActualRenderTimerIfRunning,
150 + recordElapsedActualRenderTime,
151 + resetActualRenderTimer,
152 + resumeActualRenderTimerIfPaused,
153 + recordElapsedBaseRenderTimeIfRunning,
154 + startBaseRenderTimer,
155 + stopBaseRenderTimerIfRunning,
156 +};
packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.js
+6 -8
@@ -46,11 +46,10 @@ describe('ReactFiberHostContext', () => {
46 now: function() {
47 return 0;
48 },
49 - mutation: {
50 - appendChildToContainer: function() {
51 - return null;
52 - },
49 + appendChildToContainer: function() {
50 + return null;
51 },
52 + supportsMutation: true,
53 });
54
55 const container = Renderer.createContainer(/* root: */ null);
@@ -95,11 +94,10 @@ describe('ReactFiberHostContext', () => {
94 now: function() {
95 return 0;
96 },
98 - mutation: {
99 - appendChildToContainer: function() {
100 - return null;
101 - },
97 + appendChildToContainer: function() {
98 + return null;
99 },
100 + supportsMutation: true,
101 });
102
103 const container = Renderer.createContainer(rootContext);
packages/react-reconciler/src/__tests__/ReactPersistent-test.js renamed
+13 -17
@@ -12,22 +12,26 @@
12
13 let React;
14 let ReactNoopPersistent;
15 -let ReactPortal;
15
16 describe('ReactPersistent', () => {
17 beforeEach(() => {
18 jest.resetModules();
19
21 - const ReactFeatureFlags = require('shared/ReactFeatureFlags');
22 - ReactFeatureFlags.enableMutableReconciler = false;
23 - ReactFeatureFlags.enablePersistentReconciler = true;
24 - ReactFeatureFlags.enableNoopReconciler = false;
25 -
20 React = require('react');
21 ReactNoopPersistent = require('react-noop-renderer/persistent');
28 - ReactPortal = require('shared/ReactPortal');
22 });
23
24 + // Inlined from shared folder so we can run this test on a bundle.
25 + function createPortal(children, containerInfo, implementation, key) {
26 + return {
27 + $$typeof: Symbol.for('react.portal'),
28 + key: key == null ? null : '' + key,
29 + children,
30 + containerInfo,
31 + implementation,
32 + };
33 + }
34 +
35 function render(element) {
36 ReactNoopPersistent.render(element);
37 }
@@ -160,11 +164,7 @@ describe('ReactPersistent', () => {
164 }
165 const portalContainer = {rootID: 'persistent-portal-test', children: []};
166 const emptyPortalChildSet = portalContainer.children;
163 - render(
164 - <Parent>
165 - {ReactPortal.createPortal(<Child />, portalContainer, null)}
166 - </Parent>,
167 - );
167 + render(<Parent>{createPortal(<Child />, portalContainer, null)}</Parent>);
168 ReactNoopPersistent.flush();
169
170 expect(emptyPortalChildSet).toEqual([]);
@@ -176,11 +176,7 @@ describe('ReactPersistent', () => {
176
177 render(
178 <Parent>
179 - {ReactPortal.createPortal(
180 - <Child>Hello {'World'}</Child>,
181 - portalContainer,
182 - null,
183 - )}
179 + {createPortal(<Child>Hello {'World'}</Child>, portalContainer, null)}
180 </Parent>,
181 );
182 ReactNoopPersistent.flush();
packages/react-reconciler/src/forks/ReactFiberHostConfig.art.js new
+10
@@ -0,0 +1,10 @@
1 +/**
2 + * Copyright (c) 2015-present, Facebook, Inc.
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 +export * from 'react-art/src/ReactARTHostConfig';
packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js new
+110
@@ -0,0 +1,110 @@
1 +/**
2 + * Copyright (c) 2015-present, Facebook, Inc.
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 +// This is a host config that's used for the `react-reconciler` package on npm.
11 +// It is only used by third-party renderers.
12 +//
13 +// Its API lets you pass the host config as an argument.
14 +// However, inside the `react-reconciler` we treat host config as a module.
15 +// This file is a shim between two worlds.
16 +//
17 +// It works because the `react-reconciler` bundle is wrapped in something like:
18 +//
19 +// module.exports = function ($$$config) {
20 +// /* reconciler code */
21 +// }
22 +//
23 +// So `$$$config` looks like a global variable, but it's
24 +// really an argument to a top-level wrapping function.
25 +
26 +declare var $$$hostConfig: any;
27 +export opaque type Type = mixed; // eslint-disable-line no-undef
28 +export opaque type Props = mixed; // eslint-disable-line no-undef
29 +export opaque type Container = mixed; // eslint-disable-line no-undef
30 +export opaque type Instance = mixed; // eslint-disable-line no-undef
31 +export opaque type TextInstance = mixed; // eslint-disable-line no-undef
32 +export opaque type HydratableInstance = mixed; // eslint-disable-line no-undef
33 +export opaque type PublicInstance = mixed; // eslint-disable-line no-undef
34 +export opaque type HostContext = mixed; // eslint-disable-line no-undef
35 +export opaque type UpdatePayload = mixed; // eslint-disable-line no-undef
36 +export opaque type ChildSet = mixed; // eslint-disable-line no-undef
37 +
38 +export const getPublicInstance = $$$hostConfig.getPublicInstance;
39 +export const getRootHostContext = $$$hostConfig.getRootHostContext;
40 +export const getChildHostContext = $$$hostConfig.getChildHostContext;
41 +export const prepareForCommit = $$$hostConfig.prepareForCommit;
42 +export const resetAfterCommit = $$$hostConfig.resetAfterCommit;
43 +export const createInstance = $$$hostConfig.createInstance;
44 +export const appendInitialChild = $$$hostConfig.appendInitialChild;
45 +export const finalizeInitialChildren = $$$hostConfig.finalizeInitialChildren;
46 +export const prepareUpdate = $$$hostConfig.prepareUpdate;
47 +export const shouldSetTextContent = $$$hostConfig.shouldSetTextContent;
48 +export const shouldDeprioritizeSubtree =
49 + $$$hostConfig.shouldDeprioritizeSubtree;
50 +export const createTextInstance = $$$hostConfig.createTextInstance;
51 +export const scheduleDeferredCallback = $$$hostConfig.scheduleDeferredCallback;
52 +export const cancelDeferredCallback = $$$hostConfig.cancelDeferredCallback;
53 +export const now = $$$hostConfig.now;
54 +export const isPrimaryRenderer = $$$hostConfig.isPrimaryRenderer;
55 +export const supportsMutation = $$$hostConfig.supportsMutation;
56 +export const supportsPersistence = $$$hostConfig.supportsPersistence;
57 +export const supportsHydration = $$$hostConfig.supportsHydration;
58 +
59 +// -------------------
60 +// Mutation
61 +// (optional)
62 +// -------------------
63 +export const appendChild = $$$hostConfig.appendChild;
64 +export const appendChildToContainer = $$$hostConfig.appendChildToContainer;
65 +export const commitTextUpdate = $$$hostConfig.commitTextUpdate;
66 +export const commitMount = $$$hostConfig.commitMount;
67 +export const commitUpdate = $$$hostConfig.commitUpdate;
68 +export const insertBefore = $$$hostConfig.insertBefore;
69 +export const insertInContainerBefore = $$$hostConfig.insertInContainerBefore;
70 +export const removeChild = $$$hostConfig.removeChild;
71 +export const removeChildFromContainer = $$$hostConfig.removeChildFromContainer;
72 +export const resetTextContent = $$$hostConfig.resetTextContent;
73 +
74 +// -------------------
75 +// Persistence
76 +// (optional)
77 +// -------------------
78 +export const cloneInstance = $$$hostConfig.cloneInstance;
79 +export const createContainerChildSet = $$$hostConfig.createContainerChildSet;
80 +export const appendChildToContainerChildSet =
81 + $$$hostConfig.appendChildToContainerChildSet;
82 +export const finalizeContainerChildren =
83 + $$$hostConfig.finalizeContainerChildren;
84 +export const replaceContainerChildren = $$$hostConfig.replaceContainerChildren;
85 +
86 +// -------------------
87 +// Hydration
88 +// (optional)
89 +// -------------------
90 +export const canHydrateInstance = $$$hostConfig.canHydrateInstance;
91 +export const canHydrateTextInstance = $$$hostConfig.canHydrateTextInstance;
92 +export const getNextHydratableSibling = $$$hostConfig.getNextHydratableSibling;
93 +export const getFirstHydratableChild = $$$hostConfig.getFirstHydratableChild;
94 +export const hydrateInstance = $$$hostConfig.hydrateInstance;
95 +export const hydrateTextInstance = $$$hostConfig.hydrateTextInstance;
96 +export const didNotMatchHydratedContainerTextInstance =
97 + $$$hostConfig.didNotMatchHydratedContainerTextInstance;
98 +export const didNotMatchHydratedTextInstance =
99 + $$$hostConfig.didNotMatchHydratedTextInstance;
100 +export const didNotHydrateContainerInstance =
101 + $$$hostConfig.didNotHydrateContainerInstance;
102 +export const didNotHydrateInstance = $$$hostConfig.didNotHydrateInstance;
103 +export const didNotFindHydratableContainerInstance =
104 + $$$hostConfig.didNotFindHydratableContainerInstance;
105 +export const didNotFindHydratableContainerTextInstance =
106 + $$$hostConfig.didNotFindHydratableContainerTextInstance;
107 +export const didNotFindHydratableInstance =
108 + $$$hostConfig.didNotFindHydratableInstance;
109 +export const didNotFindHydratableTextInstance =
110 + $$$hostConfig.didNotFindHydratableTextInstance;
packages/react-reconciler/src/forks/ReactFiberHostConfig.dom.js new
+10
@@ -0,0 +1,10 @@
1 +/**
2 + * Copyright (c) 2015-present, Facebook, Inc.
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 +export * from 'react-dom/src/client/ReactDOMHostConfig';
packages/react-reconciler/src/forks/ReactFiberHostConfig.fabric.js new
+10
@@ -0,0 +1,10 @@
1 +/**
2 + * Copyright (c) 2015-present, Facebook, Inc.
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 +export * from 'react-native-renderer/src/ReactFabricHostConfig';
packages/react-reconciler/src/forks/ReactFiberHostConfig.native.js new
+10
@@ -0,0 +1,10 @@
1 +/**
2 + * Copyright (c) 2015-present, Facebook, Inc.
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 +export * from 'react-native-renderer/src/ReactNativeHostConfig';
packages/react-reconciler/src/forks/ReactFiberHostConfig.test.js new
+10
@@ -0,0 +1,10 @@
1 +/**
2 + * Copyright (c) 2015-present, Facebook, Inc.
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 +export * from 'react-test-renderer/src/ReactTestHostConfig';
packages/react-scheduler/src/ReactScheduler.js
+1 -1
@@ -30,7 +30,7 @@
30 // layout, paint and other browser work is counted against the available time.
31 // The frame rate is dynamically adjusted.
32
33 -import type {Deadline} from 'react-reconciler';
33 +import type {Deadline} from 'react-reconciler/src/ReactFiberScheduler';
34 type FrameCallbackType = Deadline => void;
35 type CallbackConfigType = {|
36 scheduledCallback: FrameCallbackType,
packages/react-test-renderer/src/ReactTestHostConfig.js
+160 -150
@@ -11,6 +11,13 @@ import emptyObject from 'fbjs/lib/emptyObject';
11
12 import * as TestRendererScheduling from './ReactTestRendererScheduling';
13
14 +export type Type = string;
15 +export type Props = Object;
16 +export type Container = {|
17 + children: Array<Instance | TextInstance>,
18 + createNodeMock: Function,
19 + tag: 'CONTAINER',
20 +|};
21 export type Instance = {|
22 type: string,
23 props: Object,
@@ -18,23 +25,22 @@ export type Instance = {|
25 rootContainerInstance: Container,
26 tag: 'INSTANCE',
27 |};
21 -
28 export type TextInstance = {|
29 text: string,
30 tag: 'TEXT',
31 |};
26 -
27 -type Container = {|
28 - children: Array<Instance | TextInstance>,
29 - createNodeMock: Function,
30 - tag: 'CONTAINER',
31 -|};
32 -
33 -type Props = Object;
32 +export type HydratableInstance = Instance | TextInstance;
33 +export type PublicInstance = Instance | TextInstance;
34 +export type HostContext = Object;
35 +export type UpdatePayload = Object;
36 +export type ChildSet = void; // Unused
37
38 const UPDATE_SIGNAL = {};
39
37 -function getPublicInstance(inst: Instance | TextInstance): * {
40 +export * from 'shared/HostConfigWithNoPersistence';
41 +export * from 'shared/HostConfigWithNoHydration';
42 +
43 +export function getPublicInstance(inst: Instance | TextInstance): * {
44 switch (inst.tag) {
45 case 'INSTANCE':
46 const createNodeMock = inst.rootContainerInstance.createNodeMock;
@@ -47,7 +53,7 @@ function getPublicInstance(inst: Instance | TextInstance): * {
53 }
54 }
55
50 -function appendChild(
56 +export function appendChild(
57 parentInstance: Instance | Container,
58 child: Instance | TextInstance,
59 ): void {
@@ -58,7 +64,7 @@ function appendChild(
64 parentInstance.children.push(child);
65 }
66
61 -function insertBefore(
67 +export function insertBefore(
68 parentInstance: Instance | Container,
69 child: Instance | TextInstance,
70 beforeChild: Instance | TextInstance,
@@ -71,7 +77,7 @@ function insertBefore(
77 parentInstance.children.splice(beforeIndex, 0, child);
78 }
79
74 -function removeChild(
80 +export function removeChild(
81 parentInstance: Instance | Container,
82 child: Instance | TextInstance,
83 ): void {
@@ -79,140 +85,144 @@ function removeChild(
85 parentInstance.children.splice(index, 1);
86 }
87
82 -const ReactTestHostConfig = {
83 - getRootHostContext() {
84 - return emptyObject;
85 - },
86 -
87 - getChildHostContext() {
88 - return emptyObject;
89 - },
90 -
91 - prepareForCommit(): void {
92 - // noop
93 - },
94 -
95 - resetAfterCommit(): void {
96 - // noop
97 - },
98 -
99 - createInstance(
100 - type: string,
101 - props: Props,
102 - rootContainerInstance: Container,
103 - hostContext: Object,
104 - internalInstanceHandle: Object,
105 - ): Instance {
106 - return {
107 - type,
108 - props,
109 - children: [],
110 - rootContainerInstance,
111 - tag: 'INSTANCE',
112 - };
113 - },
114 -
115 - appendInitialChild(
116 - parentInstance: Instance,
117 - child: Instance | TextInstance,
118 - ): void {
119 - const index = parentInstance.children.indexOf(child);
120 - if (index !== -1) {
121 - parentInstance.children.splice(index, 1);
122 - }
123 - parentInstance.children.push(child);
124 - },
125 -
126 - finalizeInitialChildren(
127 - testElement: Instance,
128 - type: string,
129 - props: Props,
130 - rootContainerInstance: Container,
131 - ): boolean {
132 - return false;
133 - },
134 -
135 - prepareUpdate(
136 - testElement: Instance,
137 - type: string,
138 - oldProps: Props,
139 - newProps: Props,
140 - rootContainerInstance: Container,
141 - hostContext: Object,
142 - ): null | {} {
143 - return UPDATE_SIGNAL;
144 - },
145 -
146 - shouldSetTextContent(type: string, props: Props): boolean {
147 - return false;
148 - },
149 -
150 - shouldDeprioritizeSubtree(type: string, props: Props): boolean {
151 - return false;
152 - },
153 -
154 - createTextInstance(
155 - text: string,
156 - rootContainerInstance: Container,
157 - hostContext: Object,
158 - internalInstanceHandle: Object,
159 - ): TextInstance {
160 - return {
161 - text,
162 - tag: 'TEXT',
163 - };
164 - },
165 -
166 - getPublicInstance,
167 -
168 - scheduleDeferredCallback: TestRendererScheduling.scheduleDeferredCallback,
169 - cancelDeferredCallback: TestRendererScheduling.cancelDeferredCallback,
170 - // This approach enables `now` to be mocked by tests,
171 - // Even after the reconciler has initialized and read host config values.
172 - now: () => TestRendererScheduling.nowImplementation(),
173 -
174 - isPrimaryRenderer: true,
175 -
176 - mutation: {
177 - commitUpdate(
178 - instance: Instance,
179 - updatePayload: {},
180 - type: string,
181 - oldProps: Props,
182 - newProps: Props,
183 - internalInstanceHandle: Object,
184 - ): void {
185 - instance.type = type;
186 - instance.props = newProps;
187 - },
188 -
189 - commitMount(
190 - instance: Instance,
191 - type: string,
192 - newProps: Props,
193 - internalInstanceHandle: Object,
194 - ): void {
195 - // noop
196 - },
197 -
198 - commitTextUpdate(
199 - textInstance: TextInstance,
200 - oldText: string,
201 - newText: string,
202 - ): void {
203 - textInstance.text = newText;
204 - },
205 - resetTextContent(testElement: Instance): void {
206 - // noop
207 - },
208 -
209 - appendChild: appendChild,
210 - appendChildToContainer: appendChild,
211 - insertBefore: insertBefore,
212 - insertInContainerBefore: insertBefore,
213 - removeChild: removeChild,
214 - removeChildFromContainer: removeChild,
215 - },
216 -};
217 -
218 -export default ReactTestHostConfig;
88 +export function getRootHostContext(
89 + rootContainerInstance: Container,
90 +): HostContext {
91 + return emptyObject;
92 +}
93 +
94 +export function getChildHostContext(
95 + parentHostContext: HostContext,
96 + type: string,
97 + rootContainerInstance: Container,
98 +): HostContext {
99 + return emptyObject;
100 +}
101 +
102 +export function prepareForCommit(containerInfo: Container): void {
103 + // noop
104 +}
105 +
106 +export function resetAfterCommit(containerInfo: Container): void {
107 + // noop
108 +}
109 +
110 +export function createInstance(
111 + type: string,
112 + props: Props,
113 + rootContainerInstance: Container,
114 + hostContext: Object,
115 + internalInstanceHandle: Object,
116 +): Instance {
117 + return {
118 + type,
119 + props,
120 + children: [],
121 + rootContainerInstance,
122 + tag: 'INSTANCE',
123 + };
124 +}
125 +
126 +export function appendInitialChild(
127 + parentInstance: Instance,
128 + child: Instance | TextInstance,
129 +): void {
130 + const index = parentInstance.children.indexOf(child);
131 + if (index !== -1) {
132 + parentInstance.children.splice(index, 1);
133 + }
134 + parentInstance.children.push(child);
135 +}
136 +
137 +export function finalizeInitialChildren(
138 + testElement: Instance,
139 + type: string,
140 + props: Props,
141 + rootContainerInstance: Container,
142 + hostContext: Object,
143 +): boolean {
144 + return false;
145 +}
146 +
147 +export function prepareUpdate(
148 + testElement: Instance,
149 + type: string,
150 + oldProps: Props,
151 + newProps: Props,
152 + rootContainerInstance: Container,
153 + hostContext: Object,
154 +): null | {} {
155 + return UPDATE_SIGNAL;
156 +}
157 +
158 +export function shouldSetTextContent(type: string, props: Props): boolean {
159 + return false;
160 +}
161 +
162 +export function shouldDeprioritizeSubtree(type: string, props: Props): boolean {
163 + return false;
164 +}
165 +
166 +export function createTextInstance(
167 + text: string,
168 + rootContainerInstance: Container,
169 + hostContext: Object,
170 + internalInstanceHandle: Object,
171 +): TextInstance {
172 + return {
173 + text,
174 + tag: 'TEXT',
175 + };
176 +}
177 +
178 +export const isPrimaryRenderer = true;
179 +// This approach enables `now` to be mocked by tests,
180 +// Even after the reconciler has initialized and read host config values.
181 +export const now = () => TestRendererScheduling.nowImplementation();
182 +export const scheduleDeferredCallback =
183 + TestRendererScheduling.scheduleDeferredCallback;
184 +export const cancelDeferredCallback =
185 + TestRendererScheduling.cancelDeferredCallback;
186 +
187 +// -------------------
188 +// Mutation
189 +// -------------------
190 +
191 +export const supportsMutation = true;
192 +
193 +export function commitUpdate(
194 + instance: Instance,
195 + updatePayload: {},
196 + type: string,
197 + oldProps: Props,
198 + newProps: Props,
199 + internalInstanceHandle: Object,
200 +): void {
201 + instance.type = type;
202 + instance.props = newProps;
203 +}
204 +
205 +export function commitMount(
206 + instance: Instance,
207 + type: string,
208 + newProps: Props,
209 + internalInstanceHandle: Object,
210 +): void {
211 + // noop
212 +}
213 +
214 +export function commitTextUpdate(
215 + textInstance: TextInstance,
216 + oldText: string,
217 + newText: string,
218 +): void {
219 + textInstance.text = newText;
220 +}
221 +
222 +export function resetTextContent(testElement: Instance): void {
223 + // noop
224 +}
225 +
226 +export const appendChildToContainer = appendChild;
227 +export const insertInContainerBefore = insertBefore;
228 +export const removeChildFromContainer = removeChild;
packages/react-test-renderer/src/ReactTestRenderer.js
+2 -4
@@ -11,7 +11,7 @@ import type {Fiber} from 'react-reconciler/src/ReactFiber';
11 import type {FiberRoot} from 'react-reconciler/src/ReactFiberRoot';
12 import type {Instance, TextInstance} from './ReactTestHostConfig';
13
14 -import ReactFiberReconciler from 'react-reconciler';
14 +import * as TestRenderer from 'react-reconciler/inline.test';
15 import {batchedUpdates} from 'events/ReactGenericBatching';
16 import {findCurrentFiberUsingSlowPath} from 'react-reconciler/reflection';
17 import {
@@ -30,7 +30,7 @@ import {
30 } from 'shared/ReactTypeOfWork';
31 import invariant from 'fbjs/lib/invariant';
32
33 -import ReactTestHostConfig from './ReactTestHostConfig';
33 +import * as ReactTestHostConfig from './ReactTestHostConfig';
34 import * as TestRendererScheduling from './ReactTestRendererScheduling';
35
36 type TestRendererOptions = {
@@ -54,8 +54,6 @@ type FindOptions = $Shape<{
54
55 export type Predicate = (node: ReactTestInstance) => ?boolean;
56
57 -const TestRenderer = ReactFiberReconciler(ReactTestHostConfig);
58 -
57 const defaultTestOptions = {
58 createNodeMock: function() {
59 return null;
packages/react-test-renderer/src/ReactTestRendererScheduling.js
+1 -1
@@ -7,7 +7,7 @@
7 * @flow
8 */
9
10 -import type {Deadline} from 'react-reconciler/src/ReactFiberReconciler';
10 +import type {Deadline} from 'react-reconciler/src/ReactFiberScheduler';
11
12 // Current virtual time
13 export let nowImplementation = () => 0;
packages/shared/HostConfigWithNoHydration.js new
+39
@@ -0,0 +1,39 @@
1 +/**
2 + * Copyright (c) 2014-present, Facebook, Inc.
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 +import invariant from 'fbjs/lib/invariant';
11 +
12 +// Renderers that don't support hydration
13 +// can re-export everything from this module.
14 +
15 +function shim(...args: any) {
16 + invariant(
17 + false,
18 + 'The current renderer does not support hyration. ' +
19 + 'This error is likely caused by a bug in React. ' +
20 + 'Please file an issue.',
21 + );
22 +}
23 +
24 +// Hydration (when unsupported)
25 +export const supportsHydration = false;
26 +export const canHydrateInstance = shim;
27 +export const canHydrateTextInstance = shim;
28 +export const getNextHydratableSibling = shim;
29 +export const getFirstHydratableChild = shim;
30 +export const hydrateInstance = shim;
31 +export const hydrateTextInstance = shim;
32 +export const didNotMatchHydratedContainerTextInstance = shim;
33 +export const didNotMatchHydratedTextInstance = shim;
34 +export const didNotHydrateContainerInstance = shim;
35 +export const didNotHydrateInstance = shim;
36 +export const didNotFindHydratableContainerInstance = shim;
37 +export const didNotFindHydratableContainerTextInstance = shim;
38 +export const didNotFindHydratableInstance = shim;
39 +export const didNotFindHydratableTextInstance = shim;
packages/shared/HostConfigWithNoMutation.js new
+35
@@ -0,0 +1,35 @@
1 +/**
2 + * Copyright (c) 2014-present, Facebook, Inc.
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 +import invariant from 'fbjs/lib/invariant';
11 +
12 +// Renderers that don't support mutation
13 +// can re-export everything from this module.
14 +
15 +function shim(...args: any) {
16 + invariant(
17 + false,
18 + 'The current renderer does not support mutation. ' +
19 + 'This error is likely caused by a bug in React. ' +
20 + 'Please file an issue.',
21 + );
22 +}
23 +
24 +// Mutation (when unsupported)
25 +export const supportsMutation = false;
26 +export const appendChild = shim;
27 +export const appendChildToContainer = shim;
28 +export const commitTextUpdate = shim;
29 +export const commitMount = shim;
30 +export const commitUpdate = shim;
31 +export const insertBefore = shim;
32 +export const insertInContainerBefore = shim;
33 +export const removeChild = shim;
34 +export const removeChildFromContainer = shim;
35 +export const resetTextContent = shim;
packages/shared/HostConfigWithNoPersistence.js new
+30
@@ -0,0 +1,30 @@
1 +/**
2 + * Copyright (c) 2014-present, Facebook, Inc.
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 +import invariant from 'fbjs/lib/invariant';
11 +
12 +// Renderers that don't support persistence
13 +// can re-export everything from this module.
14 +
15 +function shim(...args: any) {
16 + invariant(
17 + false,
18 + 'The current renderer does not support persistence. ' +
19 + 'This error is likely caused by a bug in React. ' +
20 + 'Please file an issue.',
21 + );
22 +}
23 +
24 +// Persistence (when unsupported)
25 +export const supportsPersistence = false;
26 +export const cloneInstance = shim;
27 +export const createContainerChildSet = shim;
28 +export const appendChildToContainerChildSet = shim;
29 +export const finalizeContainerChildren = shim;
30 +export const replaceContainerChildren = shim;
packages/shared/ReactFeatureFlags.js
-6
@@ -12,12 +12,6 @@ import invariant from 'fbjs/lib/invariant';
12 // Exports ReactDOM.createRoot
13 export const enableUserTimingAPI = __DEV__;
14
15 -// Mutating mode (React DOM, React ART, React Native):
16 -export const enableMutatingReconciler = true;
17 -// Experimental noop mode (currently unused):
18 -export const enableNoopReconciler = false;
19 -// Experimental persistent mode (Fabric):
20 -export const enablePersistentReconciler = false;
15 // Experimental error-boundary API that can recover from errors within a single
16 // render phase
17 export const enableGetDerivedStateFromCatch = false;
packages/shared/forks/ReactFeatureFlags.native-fabric-fb.js
-5
@@ -20,11 +20,6 @@ export const enableSuspense = false;
20 export const warnAboutDeprecatedLifecycles = false;
21 export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
22 export const enableProfilerTimer = __DEV__;
23 -
24 -// React Fabric uses persistent reconciler.
25 -export const enableMutatingReconciler = false;
26 -export const enableNoopReconciler = false;
27 -export const enablePersistentReconciler = true;
23 export const fireGetDerivedStateFromPropsOnStateUpdates = true;
24
25 // Only used in www builds.
packages/shared/forks/ReactFeatureFlags.native-fabric-oss.js
-5
@@ -20,11 +20,6 @@ export const enableSuspense = false;
20 export const warnAboutDeprecatedLifecycles = false;
21 export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
22 export const enableProfilerTimer = false;
23 -
24 -// React Fabric uses persistent reconciler.
25 -export const enableMutatingReconciler = false;
26 -export const enableNoopReconciler = false;
27 -export const enablePersistentReconciler = true;
23 export const fireGetDerivedStateFromPropsOnStateUpdates = true;
24
25 // Only used in www builds.
packages/shared/forks/ReactFeatureFlags.native-fb.js
-3
@@ -26,9 +26,6 @@ export const {
26
27 // The rest of the flags are static for better dead code elimination.
28 export const enableUserTimingAPI = __DEV__;
29 -export const enableMutatingReconciler = true;
30 -export const enableNoopReconciler = false;
31 -export const enablePersistentReconciler = false;
29
30 // Only used in www builds.
31 export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.native-oss.js
-3
@@ -16,9 +16,6 @@ export const debugRenderPhaseSideEffects = false;
16 export const debugRenderPhaseSideEffectsForStrictMode = false;
17 export const enableGetDerivedStateFromCatch = false;
18 export const enableSuspense = false;
19 -export const enableMutatingReconciler = true;
20 -export const enableNoopReconciler = false;
21 -export const enablePersistentReconciler = false;
19 export const enableUserTimingAPI = __DEV__;
20 export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
21 export const warnAboutDeprecatedLifecycles = false;
packages/shared/forks/ReactFeatureFlags.persistent.js
-6
@@ -22,12 +22,6 @@ export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
22 export const enableProfilerTimer = false;
23 export const fireGetDerivedStateFromPropsOnStateUpdates = true;
24
25 -// react-reconciler/persistent entry point
26 -// uses a persistent reconciler.
27 -export const enableMutatingReconciler = false;
28 -export const enableNoopReconciler = false;
29 -export const enablePersistentReconciler = true;
30 -
25 // Only used in www builds.
26 export function addUserTimingListener() {
27 invariant(false, 'Not implemented.');
packages/shared/forks/ReactFeatureFlags.test-renderer.js
-3
@@ -19,9 +19,6 @@ export const enableGetDerivedStateFromCatch = false;
19 export const enableSuspense = false;
20 export const warnAboutDeprecatedLifecycles = false;
21 export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
22 -export const enableMutatingReconciler = true;
23 -export const enableNoopReconciler = false;
24 -export const enablePersistentReconciler = false;
22 export const enableProfilerTimer = false;
23 export const fireGetDerivedStateFromPropsOnStateUpdates = true;
24
packages/shared/forks/ReactFeatureFlags.www.js
-5
@@ -24,11 +24,6 @@ export const {
24
25 // The rest of the flags are static for better dead code elimination.
26
27 -// The www bundles only use the mutating reconciler.
28 -export const enableMutatingReconciler = true;
29 -export const enableNoopReconciler = false;
30 -export const enablePersistentReconciler = false;
31 -
27 // In www, we have experimental support for gathering data
28 // from User Timing API calls in production. By default, we
29 // only emit performance.mark/measure calls in __DEV__. But if
scripts/flow/config/flowconfig
+3
@@ -30,6 +30,9 @@ esproposal.class_static_fields=enable
30 esproposal.class_instance_fields=enable
31 unsafe.enable_getters_and_setters=true
32
33 +# Substituted by createFlowConfig.js:
34 +%REACT_RENDERER_FLOW_OPTIONS%
35 +
36 munge_underscores=false
37
38 suppress_type=$FlowIssue
scripts/flow/createFlowConfigs.js
+27 -5
@@ -1,15 +1,35 @@
1 +/**
2 + * Copyright (c) 2015-present, Facebook, Inc.
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 'use strict';
11
12 const chalk = require('chalk');
13 const fs = require('fs');
14 const mkdirp = require('mkdirp');
6 -const {typedRenderers} = require('./typedRenderers');
15 +const inlinedHostConfigs = require('../shared/inlinedHostConfigs');
16
8 -const config = fs.readFileSync(__dirname + '/config/flowconfig');
17 +const configTemplate = fs
18 + .readFileSync(__dirname + '/config/flowconfig')
19 + .toString();
20
10 -function writeConfig(folder) {
21 +function writeConfig(renderer) {
22 + const folder = __dirname + '/' + renderer;
23 mkdirp.sync(folder);
24
25 + const config = configTemplate.replace(
26 + '%REACT_RENDERER_FLOW_OPTIONS%',
27 + `
28 +module.name_mapper='react-reconciler/inline.${renderer}$$' -> 'react-reconciler/inline-typed'
29 +module.name_mapper='ReactFiberHostConfig$$' -> 'forks/ReactFiberHostConfig.${renderer}'
30 + `.trim(),
31 + );
32 +
33 const disclaimer = `
34 # ---------------------------------------------------------------#
35 # NOTE: this file is generated. #
@@ -39,6 +59,8 @@ ${disclaimer}
59
60 // Write multiple configs in different folders
61 // so that we can run those checks in parallel if we want.
42 -typedRenderers.forEach(renderer => {
43 - writeConfig(__dirname + '/' + renderer);
62 +inlinedHostConfigs.forEach(rendererInfo => {
63 + if (rendererInfo.isFlowTyped) {
64 + writeConfig(rendererInfo.shortName);
65 + }
66 });
scripts/flow/runFlow.js
+9 -3
@@ -1,3 +1,10 @@
1 +/**
2 + * Copyright (c) 2015-present, Facebook, Inc.
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 +
8 'use strict';
9
10 const chalk = require('chalk');
@@ -8,9 +15,9 @@ require('./createFlowConfigs');
15 async function runFlow(renderer, args) {
16 return new Promise(resolve => {
17 console.log(
11 - 'Running Flow for the ' + chalk.cyan(renderer) + ' renderer...',
18 + 'Running Flow on the ' + chalk.yellow(renderer) + ' renderer...',
19 );
13 - let cmd = '../../../node_modules/.bin/flow';
20 + let cmd = __dirname + '/../../node_modules/.bin/flow';
21 if (process.platform === 'win32') {
22 cmd = cmd.replace(/\//g, '\\') + '.cmd';
23 }
@@ -30,7 +37,6 @@ async function runFlow(renderer, args) {
37 console.log(
38 'Flow passed for the ' + chalk.green(renderer) + ' renderer',
39 );
33 - console.log();
40 resolve();
41 }
42 });
scripts/flow/typedRenderers.js deleted
-3
@@ -1,3 +0,0 @@
1 -'use strict';
2 -
3 -exports.typedRenderers = ['dom', 'fabric', 'native', 'test'];
scripts/jest/config.base.js new
+23
@@ -0,0 +1,23 @@
1 +'use strict';
2 +
3 +module.exports = {
4 + haste: {
5 + hasteImplModulePath: require.resolve('./noHaste.js'),
6 + },
7 + modulePathIgnorePatterns: [
8 + '<rootDir>/scripts/rollup/shims/',
9 + '<rootDir>/scripts/bench/',
10 + ],
11 + transform: {
12 + '.*': require.resolve('./preprocessor.js'),
13 + },
14 + setupFiles: [require.resolve('./setupEnvironment.js')],
15 + setupTestFrameworkScriptFile: require.resolve('./setupTests.js'),
16 + // Only include files directly in __tests__, not in nested folders.
17 + testRegex: '/__tests__/[^/]*(\\.js|\\.coffee|[^d]\\.ts)$',
18 + moduleFileExtensions: ['js', 'json', 'node', 'coffee', 'ts'],
19 + rootDir: process.cwd(),
20 + roots: ['<rootDir>/packages', '<rootDir>/scripts'],
21 + collectCoverageFrom: ['packages/**/*.js'],
22 + timers: 'fake',
23 +};
scripts/jest/config.build.js
+2 -2
@@ -2,7 +2,7 @@
2
3 const {readdirSync, statSync} = require('fs');
4 const {join} = require('path');
5 -const sourceConfig = require('./config.source');
5 +const baseConfig = require('./config.base');
6
7 // Find all folders in packages/* with package.json
8 const packagesRoot = join(__dirname, '..', '..', 'packages');
@@ -24,7 +24,7 @@ packages.forEach(name => {
24 ] = `<rootDir>/build/node_modules/${name}/$1`;
25 });
26
27 -module.exports = Object.assign({}, sourceConfig, {
27 +module.exports = Object.assign({}, baseConfig, {
28 // Redirect imports to the compiled bundles
29 moduleNameMapper,
30 // Don't run bundle tests on blacklisted -test.internal.* files
scripts/jest/config.source.js
+7 -20
@@ -1,23 +1,10 @@
1 'use strict';
2
3 -module.exports = {
4 - haste: {
5 - hasteImplModulePath: require.resolve('./noHaste.js'),
6 - },
7 - modulePathIgnorePatterns: [
8 - '<rootDir>/scripts/rollup/shims/',
9 - '<rootDir>/scripts/bench/',
3 +const baseConfig = require('./config.base');
4 +
5 +module.exports = Object.assign({}, baseConfig, {
6 + setupFiles: [
7 + ...baseConfig.setupFiles,
8 + require.resolve('./setupHostConfigs.js'),
9 ],
11 - transform: {
12 - '.*': require.resolve('./preprocessor.js'),
13 - },
14 - setupFiles: [require.resolve('./setupEnvironment.js')],
15 - setupTestFrameworkScriptFile: require.resolve('./setupTests.js'),
16 - // Only include files directly in __tests__, not in nested folders.
17 - testRegex: '/__tests__/[^/]*(\\.js|\\.coffee|[^d]\\.ts)$',
18 - moduleFileExtensions: ['js', 'json', 'node', 'coffee', 'ts'],
19 - rootDir: process.cwd(),
20 - roots: ['<rootDir>/packages', '<rootDir>/scripts'],
21 - collectCoverageFrom: ['packages/**/*.js'],
22 - timers: 'fake',
23 -};
10 +});
scripts/jest/setupEnvironment.js
+6
@@ -45,3 +45,9 @@ if (typeof window !== 'undefined') {
45 }
46 });
47 }
48 +
49 +// Preserve the empty object identity across module resets.
50 +// This is needed for some tests that rely on string refs
51 +// but reset modules between loading different renderers.
52 +const obj = require.requireActual('fbjs/lib/emptyObject');
53 +jest.mock('fbjs/lib/emptyObject', () => obj);
scripts/jest/setupHostConfigs.js new
+39
@@ -0,0 +1,39 @@
1 +'use strict';
2 +
3 +const inlinedHostConfigs = require('../shared/inlinedHostConfigs');
4 +
5 +// When testing the custom renderer code path through `react-reconciler`,
6 +// turn the export into a function, and use the argument as host config.
7 +const shimHostConfigPath = 'react-reconciler/src/ReactFiberHostConfig';
8 +jest.mock('react-reconciler', () => {
9 + return config => {
10 + jest.mock(shimHostConfigPath, () => config);
11 + return require.requireActual('react-reconciler');
12 + };
13 +});
14 +jest.mock('react-reconciler/persistent', () => {
15 + return config => {
16 + jest.mock(shimHostConfigPath, () => config);
17 + return require.requireActual('react-reconciler/persistent');
18 + };
19 +});
20 +
21 +// But for inlined host configs (such as React DOM, Native, etc), we
22 +// mock their named entry points to establish a host config mapping.
23 +inlinedHostConfigs.forEach(rendererInfo => {
24 + if (rendererInfo.shortName === 'custom') {
25 + // There is no inline entry point for the custom renderers.
26 + // Instead, it's handled by the generic `react-reconciler` entry point above.
27 + return;
28 + }
29 + jest.mock(`react-reconciler/inline.${rendererInfo.shortName}`, () => {
30 + jest.mock(shimHostConfigPath, () =>
31 + require.requireActual(
32 + `react-reconciler/src/forks/ReactFiberHostConfig.${
33 + rendererInfo.shortName
34 + }.js`
35 + )
36 + );
37 + return require.requireActual('react-reconciler');
38 + });
39 +});
scripts/rollup/build.js
+1 -1
@@ -207,7 +207,7 @@ function getPlugins(
207 modulesToStub
208 ) {
209 const findAndRecordErrorCodes = extractErrorCodes(errorCodeOpts);
210 - const forks = Modules.getForks(bundleType, entry);
210 + const forks = Modules.getForks(bundleType, entry, moduleType);
211 const isProduction = isProductionBundleType(bundleType);
212 const isInGlobalScope = bundleType === UMD_DEV || bundleType === UMD_PROD;
213 const isFBBundle = bundleType === FB_WWW_DEV || bundleType === FB_WWW_PROD;
scripts/rollup/bundles.js
+6 -3
@@ -29,6 +29,7 @@ const moduleTypes = {
29 RENDERER: 'RENDERER',
30 RENDERER_UTILS: 'RENDERER_UTILS',
31 RECONCILER: 'RECONCILER',
32 + NON_FIBER_RENDERER: 'NON_FIBER_RENDERER',
33 };
34
35 // React
@@ -39,6 +40,8 @@ const RENDERER = moduleTypes.RENDERER;
40 const RENDERER_UTILS = moduleTypes.RENDERER_UTILS;
41 // Standalone reconciler for third-party renderers.
42 const RECONCILER = moduleTypes.RECONCILER;
43 +// Non-Fiber implementations like SSR and Shallow renderers.
44 +const NON_FIBER_RENDERER = moduleTypes.NON_FIBER_RENDERER;
45
46 const bundles = [
47 /******* Isomorphic *******/
@@ -113,7 +116,7 @@ const bundles = [
116 FB_WWW_DEV,
117 FB_WWW_PROD,
118 ],
116 - moduleType: RENDERER,
119 + moduleType: NON_FIBER_RENDERER,
120 entry: 'react-dom/server.browser',
121 global: 'ReactDOMServer',
122 externals: ['react'],
@@ -122,7 +125,7 @@ const bundles = [
125 {
126 label: 'dom-server-node',
127 bundleTypes: [NODE_DEV, NODE_PROD],
125 - moduleType: RENDERER,
128 + moduleType: NON_FIBER_RENDERER,
129 entry: 'react-dom/server.node',
130 externals: ['react', 'stream'],
131 },
@@ -246,7 +249,7 @@ const bundles = [
249 {
250 label: 'test-shallow',
251 bundleTypes: [FB_WWW_DEV, NODE_DEV, NODE_PROD, UMD_DEV, UMD_PROD],
249 - moduleType: RENDERER,
252 + moduleType: NON_FIBER_RENDERER,
253 entry: 'react-test-renderer/shallow',
254 global: 'ReactShallowRenderer',
255 externals: ['react'],
scripts/rollup/forks.js
+31
@@ -1,6 +1,8 @@
1 'use strict';
2
3 const bundleTypes = require('./bundles').bundleTypes;
4 +const moduleTypes = require('./bundles').moduleTypes;
5 +const inlinedHostConfigs = require('../shared/inlinedHostConfigs');
6
7 const UMD_DEV = bundleTypes.UMD_DEV;
8 const UMD_PROD = bundleTypes.UMD_PROD;
@@ -10,6 +12,8 @@ const RN_OSS_DEV = bundleTypes.RN_OSS_DEV;
12 const RN_OSS_PROD = bundleTypes.RN_OSS_PROD;
13 const RN_FB_DEV = bundleTypes.RN_FB_DEV;
14 const RN_FB_PROD = bundleTypes.RN_FB_PROD;
15 +const RENDERER = moduleTypes.RENDERER;
16 +const RECONCILER = moduleTypes.RECONCILER;
17
18 // If you need to replace a file with another file for a specific environment,
19 // add it to this list with the logic for choosing the right replacement.
@@ -143,6 +147,33 @@ const forks = Object.freeze({
147 }
148 },
149
150 + 'react-reconciler/src/ReactFiberHostConfig': (
151 + bundleType,
152 + entry,
153 + dependencies,
154 + moduleType
155 + ) => {
156 + if (dependencies.indexOf('react-reconciler') !== -1) {
157 + return null;
158 + }
159 + if (moduleType !== RENDERER && moduleType !== RECONCILER) {
160 + return null;
161 + }
162 + // eslint-disable-next-line no-for-of-loops/no-for-of-loops
163 + for (let rendererInfo of inlinedHostConfigs) {
164 + if (rendererInfo.entryPoints.indexOf(entry) !== -1) {
165 + return `react-reconciler/src/forks/ReactFiberHostConfig.${
166 + rendererInfo.shortName
167 + }.js`;
168 + }
169 + }
170 + throw new Error(
171 + 'Expected ReactFiberHostConfig to always be replaced with a shim, but ' +
172 + `found no mention of "${entry}" entry point in ./scripts/shared/inlinedHostConfigs.js. ` +
173 + 'Did you mean to add it there to associate it with a specific renderer?'
174 + );
175 + },
176 +
177 // We wrap top-level listeners into guards on www.
178 'react-dom/src/events/EventListener': (bundleType, entry) => {
179 switch (bundleType) {
scripts/rollup/modules.js
+7 -2
@@ -56,11 +56,16 @@ function getDependencies(bundleType, entry) {
56 }
57
58 // Hijacks some modules for optimization and integration reasons.
59 -function getForks(bundleType, entry) {
59 +function getForks(bundleType, entry, moduleType) {
60 const forksForBundle = {};
61 Object.keys(forks).forEach(srcModule => {
62 const dependencies = getDependencies(bundleType, entry);
63 - const targetModule = forks[srcModule](bundleType, entry, dependencies);
63 + const targetModule = forks[srcModule](
64 + bundleType,
65 + entry,
66 + dependencies,
67 + moduleType
68 + );
69 if (targetModule === null) {
70 return;
71 }
scripts/rollup/wrappers.js
+12 -12
@@ -56,7 +56,8 @@ ${license}
56 'use strict';
57
58 ${
59 - globalName === 'ReactNoopRenderer'
59 + globalName === 'ReactNoopRenderer' ||
60 + globalName === 'ReactNoopRendererPersistent'
61 ? // React Noop needs regenerator runtime because it uses
62 // generators but GCC doesn't handle them in the output.
63 // So we use Babel for them.
@@ -79,7 +80,8 @@ ${source}
80 ${license}
81 */
82 ${
82 - globalName === 'ReactNoopRenderer'
83 + globalName === 'ReactNoopRenderer' ||
84 + globalName === 'ReactNoopRendererPersistent'
85 ? // React Noop needs regenerator runtime because it uses
86 // generators but GCC doesn't handle them in the output.
87 // So we use Babel for them.
@@ -200,14 +202,11 @@ ${license}
202 'use strict';
203
204 if (process.env.NODE_ENV !== "production") {
203 - // This is a hacky way to ensure third party renderers don't share
204 - // top-level module state inside the reconciler. Ideally we should
205 - // remove this hack by putting all top-level state into the closures
206 - // and then forbidding adding more of it in the reconciler.
207 - var $$$reconciler;
208 - module.exports = function(config) {
205 + module.exports = function $$$reconciler($$$hostConfig) {
206 ${source}
210 - return ($$$reconciler || ($$$reconciler = module.exports))(config);
207 + var $$$renderer = module.exports;
208 + module.exports = $$$reconciler;
209 + return $$$renderer;
210 };
211 }`;
212 },
@@ -219,10 +218,11 @@ ${source}
218 *
219 ${license}
220 */
222 -var $$$reconciler;
223 -module.exports = function(config) {
221 +module.exports = function $$$reconciler($$$hostConfig) {
222 ${source}
225 - return ($$$reconciler || ($$$reconciler = module.exports))(config);
223 + var $$$renderer = module.exports;
224 + module.exports = $$$reconciler;
225 + return $$$renderer;
226 };`;
227 },
228 };
scripts/shared/inlinedHostConfigs.js new
+40
@@ -0,0 +1,40 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
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 +'use strict';
8 +
9 +module.exports = [
10 + {
11 + shortName: 'dom',
12 + entryPoints: ['react-dom'],
13 + isFlowTyped: true,
14 + },
15 + {
16 + shortName: 'art',
17 + entryPoints: ['react-art'],
18 + isFlowTyped: false, // TODO: type it.
19 + },
20 + {
21 + shortName: 'native',
22 + entryPoints: ['react-native-renderer'],
23 + isFlowTyped: true,
24 + },
25 + {
26 + shortName: 'fabric',
27 + entryPoints: ['react-native-renderer/fabric'],
28 + isFlowTyped: true,
29 + },
30 + {
31 + shortName: 'test',
32 + entryPoints: ['react-test-renderer'],
33 + isFlowTyped: true,
34 + },
35 + {
36 + shortName: 'custom',
37 + entryPoints: ['react-reconciler', 'react-reconciler/persistent'],
38 + isFlowTyped: true,
39 + },
40 +];
scripts/tasks/flow-ci.js
+6 -3
@@ -12,12 +12,15 @@ process.on('unhandledRejection', err => {
12 });
13
14 const runFlow = require('../flow/runFlow');
15 -const {typedRenderers} = require('../flow/typedRenderers');
15 +const inlinedHostConfigs = require('../shared/inlinedHostConfigs');
16
17 async function checkAll() {
18 // eslint-disable-next-line no-for-of-loops/no-for-of-loops
19 - for (let renderer of typedRenderers) {
20 - await runFlow(renderer, ['check']);
19 + for (let rendererInfo of inlinedHostConfigs) {
20 + if (rendererInfo.isFlowTyped) {
21 + await runFlow(rendererInfo.shortName, ['check']);
22 + console.log();
23 + }
24 }
25 }
26
scripts/tasks/flow.js
+10 -6
@@ -13,21 +13,25 @@ process.on('unhandledRejection', err => {
13
14 const chalk = require('chalk');
15 const runFlow = require('../flow/runFlow');
16 -const {typedRenderers} = require('../flow/typedRenderers');
16 +const inlinedHostConfigs = require('../shared/inlinedHostConfigs');
17
18 // This script is using `flow status` for a quick check with a server.
19 // Use it for local development.
20
21 -const primaryRenderer = process.argv[2];
22 -if (typedRenderers.indexOf(primaryRenderer) === -1) {
21 +const primaryRenderer = inlinedHostConfigs.find(
22 + info => info.isFlowTyped && info.shortName === process.argv[2]
23 +);
24 +if (!primaryRenderer) {
25 console.log(
26 'The ' +
27 chalk.red('yarn flow') +
28 ' command now requires you to pick a primary renderer:'
29 );
30 console.log();
29 - typedRenderers.forEach(renderer => {
30 - console.log(' * ' + chalk.cyan('yarn flow ' + renderer));
31 + inlinedHostConfigs.forEach(rendererInfo => {
32 + if (rendererInfo.isFlowTyped) {
33 + console.log(' * ' + chalk.cyan('yarn flow ' + rendererInfo.shortName));
34 + }
35 });
36 console.log();
37 console.log('If you are not sure, run ' + chalk.green('yarn flow dom') + '.');
@@ -45,4 +49,4 @@ if (typedRenderers.indexOf(primaryRenderer) === -1) {
49 process.exit(1);
50 }
51
48 -runFlow(primaryRenderer, ['status']);
52 +runFlow(primaryRenderer.shortName, ['status']);