Skip to content

Commit be51e6a

Browse files
authored
Opt into unsafe lifecycle warnings without async tree (#12083)
Added new StrictMode component for enabling async warnings (without enabling async rendering). This component can be used in the future to help with other warnings (eg compilation, Fabric).
1 parent 431dca9 commit be51e6a

13 files changed

+278
-69
lines changed

packages/react-reconciler/src/ReactFiber.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,21 @@ import {
2626
CallComponent,
2727
ReturnComponent,
2828
Fragment,
29+
Mode,
2930
} from 'shared/ReactTypeOfWork';
3031
import getComponentName from 'shared/getComponentName';
3132

3233
import {NoWork} from './ReactFiberExpirationTime';
33-
import {NoContext, AsyncUpdates} from './ReactTypeOfInternalContext';
34+
import {
35+
NoContext,
36+
AsyncUpdates,
37+
StrictMode,
38+
} from './ReactTypeOfInternalContext';
3439
import {
3540
REACT_FRAGMENT_TYPE,
3641
REACT_RETURN_TYPE,
3742
REACT_CALL_TYPE,
43+
REACT_STRICT_MODE_TYPE,
3844
} from 'shared/ReactSymbols';
3945

4046
let hasBadMapPolyfill;
@@ -293,7 +299,7 @@ export function createWorkInProgress(
293299
}
294300

295301
export function createHostRootFiber(isAsync): Fiber {
296-
const internalContextTag = isAsync ? AsyncUpdates : NoContext;
302+
const internalContextTag = isAsync ? AsyncUpdates | StrictMode : NoContext;
297303
return createFiber(HostRoot, null, null, internalContextTag);
298304
}
299305

@@ -333,6 +339,15 @@ export function createFiberFromElement(
333339
expirationTime,
334340
key,
335341
);
342+
case REACT_STRICT_MODE_TYPE:
343+
fiber = createFiber(
344+
Mode,
345+
pendingProps,
346+
key,
347+
internalContextTag | StrictMode,
348+
);
349+
fiber.type = REACT_STRICT_MODE_TYPE;
350+
break;
336351
case REACT_CALL_TYPE:
337352
fiber = createFiber(
338353
CallComponent,

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
CallHandlerPhase,
2727
ReturnComponent,
2828
Fragment,
29+
Mode,
2930
} from 'shared/ReactTypeOfWork';
3031
import {
3132
PerformedWork,
@@ -160,6 +161,22 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
160161
return workInProgress.child;
161162
}
162163

164+
function updateMode(current, workInProgress) {
165+
const nextChildren = workInProgress.pendingProps.children;
166+
if (hasContextChanged()) {
167+
// Normally we can bail out on props equality but if context has changed
168+
// we don't do the bailout and we have to reuse existing props instead.
169+
} else if (
170+
nextChildren === null ||
171+
workInProgress.memoizedProps === nextChildren
172+
) {
173+
return bailoutOnAlreadyFinishedWork(current, workInProgress);
174+
}
175+
reconcileChildren(current, workInProgress, nextChildren);
176+
memoizeProps(workInProgress, nextChildren);
177+
return workInProgress.child;
178+
}
179+
163180
function markRef(current: Fiber | null, workInProgress: Fiber) {
164181
const ref = workInProgress.ref;
165182
if (ref !== null && (!current || current.ref !== ref)) {
@@ -777,6 +794,8 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
777794
);
778795
case Fragment:
779796
return updateFragment(current, workInProgress);
797+
case Mode:
798+
return updateMode(current, workInProgress);
780799
default:
781800
invariant(
782801
false,

packages/react-reconciler/src/ReactFiberClassComponent.js

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
enableAsyncSubtreeAPI,
1717
warnAboutDeprecatedLifecycles,
1818
} from 'shared/ReactFeatureFlags';
19-
import ReactDebugAsyncWarnings from './ReactDebugAsyncWarnings';
19+
import ReactStrictModeWarnings from './ReactStrictModeWarnings';
2020
import {isMounted} from 'react-reconciler/reflection';
2121
import * as ReactInstanceMap from 'shared/ReactInstanceMap';
2222
import emptyObject from 'fbjs/lib/emptyObject';
@@ -26,7 +26,7 @@ import invariant from 'fbjs/lib/invariant';
2626
import warning from 'fbjs/lib/warning';
2727

2828
import {startPhaseTimer, stopPhaseTimer} from './ReactDebugFiberPerf';
29-
import {AsyncUpdates} from './ReactTypeOfInternalContext';
29+
import {AsyncUpdates, StrictMode} from './ReactTypeOfInternalContext';
3030
import {
3131
cacheContext,
3232
getMaskedContext,
@@ -639,20 +639,20 @@ export default function(
639639
instance.refs = emptyObject;
640640
instance.context = getMaskedContext(workInProgress, unmaskedContext);
641641

642-
if (
643-
enableAsyncSubtreeAPI &&
644-
workInProgress.type != null &&
645-
workInProgress.type.prototype != null &&
646-
workInProgress.type.prototype.unstable_isAsyncReactComponent === true
647-
) {
648-
workInProgress.internalContextTag |= AsyncUpdates;
642+
if (workInProgress.type != null && workInProgress.type.prototype != null) {
643+
const prototype = workInProgress.type.prototype;
644+
645+
if (enableAsyncSubtreeAPI) {
646+
if (prototype.unstable_isAsyncReactComponent === true) {
647+
workInProgress.internalContextTag |= AsyncUpdates;
648+
workInProgress.internalContextTag |= StrictMode;
649+
}
650+
}
649651
}
650652

651653
if (__DEV__) {
652-
// If we're inside of an async sub-tree,
653-
// Warn about any unsafe lifecycles on this class component.
654-
if (workInProgress.internalContextTag & AsyncUpdates) {
655-
ReactDebugAsyncWarnings.recordLifecycleWarnings(
654+
if (workInProgress.internalContextTag & StrictMode) {
655+
ReactStrictModeWarnings.recordLifecycleWarnings(
656656
workInProgress,
657657
instance,
658658
);

packages/react-reconciler/src/ReactFiberCompleteWork.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
CallHandlerPhase,
3232
ReturnComponent,
3333
Fragment,
34+
Mode,
3435
} from 'shared/ReactTypeOfWork';
3536
import {Placement, Ref, Update} from 'shared/ReactTypeOfSideEffect';
3637
import invariant from 'fbjs/lib/invariant';
@@ -576,6 +577,8 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
576577
return null;
577578
case Fragment:
578579
return null;
580+
case Mode:
581+
return null;
579582
case HostPortal:
580583
popHostContainer(workInProgress);
581584
updateHostContainer(workInProgress);

packages/react-reconciler/src/ReactFiberScheduler.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import type {ExpirationTime} from './ReactFiberExpirationTime';
1616
import {getStackAddendumByWorkInProgressFiber} from 'shared/ReactFiberComponentTreeHook';
1717
import ReactErrorUtils from 'shared/ReactErrorUtils';
1818
import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
19-
import ReactDebugAsyncWarnings from './ReactDebugAsyncWarnings';
19+
import ReactStrictModeWarnings from './ReactStrictModeWarnings';
2020
import {
2121
PerformedWork,
2222
Placement,
@@ -312,7 +312,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
312312

313313
function commitAllLifeCycles() {
314314
if (__DEV__) {
315-
ReactDebugAsyncWarnings.flushPendingAsyncWarnings();
315+
ReactStrictModeWarnings.flushPendingAsyncWarnings();
316316
}
317317

318318
while (nextEffect !== null) {
@@ -657,7 +657,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
657657

658658
function performFailedUnitOfWork(workInProgress: Fiber): Fiber | null {
659659
if (__DEV__) {
660-
ReactDebugAsyncWarnings.discardPendingWarnings();
660+
ReactStrictModeWarnings.discardPendingWarnings();
661661
}
662662

663663
// The current, flushed, state of this fiber is the alternate.

packages/react-reconciler/src/ReactDebugAsyncWarnings.js renamed to packages/react-reconciler/src/ReactStrictModeWarnings.js

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type {Fiber} from './ReactFiber';
1111

1212
import getComponentName from 'shared/getComponentName';
1313
import {getStackAddendumByWorkInProgressFiber} from 'shared/ReactFiberComponentTreeHook';
14-
import {AsyncUpdates} from './ReactTypeOfInternalContext';
14+
import {StrictMode} from './ReactTypeOfInternalContext';
1515
import warning from 'fbjs/lib/warning';
1616

1717
type LIFECYCLE =
@@ -21,7 +21,7 @@ type LIFECYCLE =
2121
type LifecycleToComponentsMap = {[lifecycle: LIFECYCLE]: Array<Fiber>};
2222
type FiberToLifecycleMap = Map<Fiber, LifecycleToComponentsMap>;
2323

24-
const ReactDebugAsyncWarnings = {
24+
const ReactStrictModeWarnings = {
2525
discardPendingWarnings(): void {},
2626
flushPendingAsyncWarnings(): void {},
2727
recordLifecycleWarnings(fiber: Fiber, instance: any): void {},
@@ -39,13 +39,13 @@ if (__DEV__) {
3939
// Tracks components we have already warned about.
4040
const didWarnSet = new Set();
4141

42-
ReactDebugAsyncWarnings.discardPendingWarnings = () => {
42+
ReactStrictModeWarnings.discardPendingWarnings = () => {
4343
pendingWarningsMap = new Map();
4444
};
4545

46-
ReactDebugAsyncWarnings.flushPendingAsyncWarnings = () => {
46+
ReactStrictModeWarnings.flushPendingAsyncWarnings = () => {
4747
((pendingWarningsMap: any): FiberToLifecycleMap).forEach(
48-
(lifecycleWarningsMap, asyncRoot) => {
48+
(lifecycleWarningsMap, strictRoot) => {
4949
const lifecyclesWarningMesages = [];
5050

5151
Object.keys(lifecycleWarningsMap).forEach(lifecycle => {
@@ -71,17 +71,17 @@ if (__DEV__) {
7171
});
7272

7373
if (lifecyclesWarningMesages.length > 0) {
74-
const asyncRootComponentStack = getStackAddendumByWorkInProgressFiber(
75-
asyncRoot,
74+
const strictRootComponentStack = getStackAddendumByWorkInProgressFiber(
75+
strictRoot,
7676
);
7777

7878
warning(
7979
false,
80-
'Unsafe lifecycle methods were found within the following async tree:%s' +
80+
'Unsafe lifecycle methods were found within a strict-mode tree:%s' +
8181
'\n\n%s' +
8282
'\n\nLearn more about this warning here:' +
83-
'\nhttps://fb.me/react-async-component-lifecycle-hooks',
84-
asyncRootComponentStack,
83+
'\nhttps://fb.me/react-strict-mode-warnings',
84+
strictRootComponentStack,
8585
lifecyclesWarningMesages.join('\n\n'),
8686
);
8787
}
@@ -91,25 +91,25 @@ if (__DEV__) {
9191
pendingWarningsMap = new Map();
9292
};
9393

94-
const getAsyncRoot = (fiber: Fiber): Fiber => {
95-
let maybeAsyncRoot = null;
94+
const getStrictRoot = (fiber: Fiber): Fiber => {
95+
let maybeStrictRoot = null;
9696

9797
while (fiber !== null) {
98-
if (fiber.internalContextTag & AsyncUpdates) {
99-
maybeAsyncRoot = fiber;
98+
if (fiber.internalContextTag & StrictMode) {
99+
maybeStrictRoot = fiber;
100100
}
101101

102102
fiber = fiber.return;
103103
}
104104

105-
return maybeAsyncRoot;
105+
return maybeStrictRoot;
106106
};
107107

108-
ReactDebugAsyncWarnings.recordLifecycleWarnings = (
108+
ReactStrictModeWarnings.recordLifecycleWarnings = (
109109
fiber: Fiber,
110110
instance: any,
111111
) => {
112-
const asyncRoot = getAsyncRoot(fiber);
112+
const strictRoot = getStrictRoot(fiber);
113113

114114
// Dedup strategy: Warn once per component.
115115
// This is difficult to track any other way since component names
@@ -121,16 +121,16 @@ if (__DEV__) {
121121
}
122122

123123
let warningsForRoot;
124-
if (!pendingWarningsMap.has(asyncRoot)) {
124+
if (!pendingWarningsMap.has(strictRoot)) {
125125
warningsForRoot = {
126126
UNSAFE_componentWillMount: [],
127127
UNSAFE_componentWillReceiveProps: [],
128128
UNSAFE_componentWillUpdate: [],
129129
};
130130

131-
pendingWarningsMap.set(asyncRoot, warningsForRoot);
131+
pendingWarningsMap.set(strictRoot, warningsForRoot);
132132
} else {
133-
warningsForRoot = pendingWarningsMap.get(asyncRoot);
133+
warningsForRoot = pendingWarningsMap.get(strictRoot);
134134
}
135135

136136
const unsafeLifecycles = [];
@@ -163,4 +163,4 @@ if (__DEV__) {
163163
};
164164
}
165165

166-
export default ReactDebugAsyncWarnings;
166+
export default ReactStrictModeWarnings;

packages/react-reconciler/src/ReactTypeOfInternalContext.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@
99

1010
export type TypeOfInternalContext = number;
1111

12-
export const NoContext = 0;
13-
export const AsyncUpdates = 1;
12+
export const NoContext = 0b00000000;
13+
export const AsyncUpdates = 0b00000001;
14+
export const StrictMode = 0b00000010;

packages/react/src/React.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77

88
import assign from 'object-assign';
99
import ReactVersion from 'shared/ReactVersion';
10-
import {REACT_FRAGMENT_TYPE} from 'shared/ReactSymbols';
10+
import {REACT_FRAGMENT_TYPE, REACT_STRICT_MODE_TYPE} from 'shared/ReactSymbols';
1111

12-
import {Component, PureComponent, AsyncComponent} from './ReactBaseClasses';
12+
import {AsyncComponent, Component, PureComponent} from './ReactBaseClasses';
1313
import {forEach, map, count, toArray, only} from './ReactChildren';
1414
import ReactCurrentOwner from './ReactCurrentOwner';
1515
import {
@@ -39,6 +39,7 @@ const React = {
3939
unstable_AsyncComponent: AsyncComponent,
4040

4141
Fragment: REACT_FRAGMENT_TYPE,
42+
StrictMode: REACT_STRICT_MODE_TYPE,
4243

4344
createElement: __DEV__ ? createElementWithValidation : createElement,
4445
cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement,

packages/react/src/ReactBaseClasses.js

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -117,34 +117,32 @@ if (__DEV__) {
117117
}
118118
}
119119

120+
function ComponentDummy() {}
121+
ComponentDummy.prototype = Component.prototype;
122+
120123
/**
121-
* Base class helpers for the updating state of a component.
124+
* Convenience component with default shallow equality check for sCU.
122125
*/
123126
function PureComponent(props, context, updater) {
124-
// Duplicated from Component.
125127
this.props = props;
126128
this.context = context;
127129
this.refs = emptyObject;
128-
// We initialize the default updater but the real one gets injected by the
129-
// renderer.
130130
this.updater = updater || ReactNoopUpdateQueue;
131131
}
132132

133-
function ComponentDummy() {}
134-
ComponentDummy.prototype = Component.prototype;
135133
const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
136134
pureComponentPrototype.constructor = PureComponent;
137135
// Avoid an extra prototype jump for these methods.
138136
Object.assign(pureComponentPrototype, Component.prototype);
139137
pureComponentPrototype.isPureReactComponent = true;
140138

139+
/**
140+
* Special component type that opts subtree into async rendering mode.
141+
*/
141142
function AsyncComponent(props, context, updater) {
142-
// Duplicated from Component.
143143
this.props = props;
144144
this.context = context;
145145
this.refs = emptyObject;
146-
// We initialize the default updater but the real one gets injected by the
147-
// renderer.
148146
this.updater = updater || ReactNoopUpdateQueue;
149147
}
150148

@@ -157,4 +155,4 @@ asyncComponentPrototype.render = function() {
157155
return this.props.children;
158156
};
159157

160-
export {Component, PureComponent, AsyncComponent};
158+
export {AsyncComponent, Component, PureComponent};

packages/react/src/ReactElementValidator.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@
1515
import lowPriorityWarning from 'shared/lowPriorityWarning';
1616
import describeComponentFrame from 'shared/describeComponentFrame';
1717
import getComponentName from 'shared/getComponentName';
18-
import {getIteratorFn, REACT_FRAGMENT_TYPE} from 'shared/ReactSymbols';
18+
import {
19+
getIteratorFn,
20+
REACT_FRAGMENT_TYPE,
21+
REACT_STRICT_MODE_TYPE,
22+
} from 'shared/ReactSymbols';
1923
import checkPropTypes from 'prop-types/checkPropTypes';
2024
import warning from 'fbjs/lib/warning';
2125

@@ -282,7 +286,8 @@ export function createElementWithValidation(type, props, children) {
282286
typeof type === 'string' ||
283287
typeof type === 'function' ||
284288
// Note: its typeof might be other than 'symbol' or 'number' if it's a polyfill.
285-
type === REACT_FRAGMENT_TYPE;
289+
type === REACT_FRAGMENT_TYPE ||
290+
type === REACT_STRICT_MODE_TYPE;
286291

287292
// We warn in this case but don't throw. We expect the element creation to
288293
// succeed and there will likely be errors in render.

0 commit comments

Comments
 (0)