pure (#13748)
* pure A higher-order component version of the `React.PureComponent` class. During an update, the previous props are compared to the new props. If they are the same, React will skip rendering the component and its children. Unlike userspace implementations, `pure` will not add an additional fiber to the tree. The first argument must be a functional component; it does not work with classes. `pure` uses shallow comparison by default, like `React.PureComponent`. A custom comparison can be passed as the second argument. Co-authored-by: Andrew Clark <acdlite@fb.com> Co-authored-by: Sophie Alpert <sophiebits@fb.com> * Warn if first argument is not a functional component
Andrew Clark committed
Sep 27, 2018 at 15:25 UTC
a0733fe13d598303f45b9d27c84ce0ec40d09021
10 files changed
+433
-70
packages/react-reconciler/src/ReactFiber.js
+15
-14
@@ -37,6 +37,8 @@ import {
37
FunctionalComponentLazy,
38
ClassComponentLazy,
39
ForwardRefLazy,
40
+ PureComponent,
41
+ PureComponentLazy,
42
} from 'shared/ReactWorkTags';
43
import getComponentName from 'shared/getComponentName';
44
@@ -57,6 +59,7 @@ import {
59
REACT_CONTEXT_TYPE,
60
REACT_CONCURRENT_MODE_TYPE,
61
REACT_PLACEHOLDER_TYPE,
62
+ REACT_PURE_TYPE,
63
} from 'shared/ReactSymbols';
64
65
let hasBadMapPolyfill;
@@ -300,12 +303,14 @@ export function resolveLazyComponentTag(
303
return shouldConstruct(Component)
304
? ClassComponentLazy
305
: FunctionalComponentLazy;
303
- } else if (
304
- Component !== undefined &&
305
- Component !== null &&
306
- Component.$$typeof
307
- ) {
308
- return ForwardRefLazy;
306
+ } else if (Component !== undefined && Component !== null) {
307
+ const $$typeof = Component.$$typeof;
308
+ if ($$typeof === REACT_FORWARD_REF_TYPE) {
309
+ return ForwardRefLazy;
310
+ }
311
+ if ($$typeof === REACT_PURE_TYPE) {
312
+ return PureComponentLazy;
313
+ }
314
}
315
return IndeterminateComponent;
316
}
@@ -363,15 +368,8 @@ export function createWorkInProgress(
368
}
369
}
370
366
- // Don't touching the subtree's expiration time, which has not changed.
371
workInProgress.childExpirationTime = current.childExpirationTime;
368
- if (pendingProps !== current.pendingProps) {
369
- // This fiber has new props.
370
- workInProgress.expirationTime = expirationTime;
371
- } else {
372
- // This fiber's props have not changed.
373
- workInProgress.expirationTime = current.expirationTime;
374
- }
372
+ workInProgress.expirationTime = current.expirationTime;
373
374
workInProgress.child = current.child;
375
workInProgress.memoizedProps = current.memoizedProps;
@@ -460,6 +458,9 @@ export function createFiberFromElement(
458
case REACT_FORWARD_REF_TYPE:
459
fiberTag = ForwardRef;
460
break getTag;
461
+ case REACT_PURE_TYPE:
462
+ fiberTag = PureComponent;
463
+ break getTag;
464
default: {
465
if (typeof type.then === 'function') {
466
fiberTag = IndeterminateComponent;
packages/react-reconciler/src/ReactFiberBeginWork.js
+163
-55
@@ -31,6 +31,8 @@ import {
31
ContextConsumer,
32
Profiler,
33
PlaceholderComponent,
34
+ PureComponent,
35
+ PureComponentLazy,
36
} from 'shared/ReactWorkTags';
37
import {
38
NoEffect,
@@ -52,6 +54,7 @@ import {
54
enableSchedulerTracing,
55
} from 'shared/ReactFeatureFlags';
56
import invariant from 'shared/invariant';
57
+import shallowEqual from 'shared/shallowEqual';
58
import getComponentName from 'shared/getComponentName';
59
import ReactStrictModeWarnings from './ReactStrictModeWarnings';
60
import warning from 'shared/warning';
@@ -196,6 +199,58 @@ function updateForwardRef(
199
return workInProgress.child;
200
}
201
202
+function updatePureComponent(
203
+ current: Fiber | null,
204
+ workInProgress: Fiber,
205
+ Component: any,
206
+ nextProps: any,
207
+ updateExpirationTime,
208
+ renderExpirationTime: ExpirationTime,
209
+) {
210
+ const render = Component.render;
211
+
212
+ if (
213
+ current !== null &&
214
+ (updateExpirationTime === NoWork ||
215
+ updateExpirationTime > renderExpirationTime)
216
+ ) {
217
+ const prevProps = current.memoizedProps;
218
+ // Default to shallow comparison
219
+ let compare = Component.compare;
220
+ compare = compare !== null ? compare : shallowEqual;
221
+ if (compare(prevProps, nextProps)) {
222
+ return bailoutOnAlreadyFinishedWork(
223
+ current,
224
+ workInProgress,
225
+ renderExpirationTime,
226
+ );
227
+ }
228
+ }
229
+
230
+ // The rest is a fork of updateFunctionalComponent
231
+ let nextChildren;
232
+ prepareToReadContext(workInProgress, renderExpirationTime);
233
+ if (__DEV__) {
234
+ ReactCurrentOwner.current = workInProgress;
235
+ ReactCurrentFiber.setCurrentPhase('render');
236
+ nextChildren = render(nextProps);
237
+ ReactCurrentFiber.setCurrentPhase(null);
238
+ } else {
239
+ nextChildren = render(nextProps);
240
+ }
241
+
242
+ // React DevTools reads this flag.
243
+ workInProgress.effectTag |= PerformedWork;
244
+ reconcileChildren(
245
+ current,
246
+ workInProgress,
247
+ nextChildren,
248
+ renderExpirationTime,
249
+ );
250
+ memoizeProps(workInProgress, nextProps);
251
+ return workInProgress.child;
252
+}
253
+
254
function updateFragment(
255
current: Fiber | null,
256
workInProgress: Fiber,
@@ -617,6 +672,7 @@ function mountIndeterminateComponent(
672
current,
673
workInProgress,
674
Component,
675
+ updateExpirationTime,
676
renderExpirationTime,
677
) {
678
invariant(
@@ -637,37 +693,53 @@ function mountIndeterminateComponent(
693
Component,
694
));
695
const resolvedProps = resolveDefaultProps(Component, props);
696
+ let child;
697
switch (resolvedTag) {
698
case FunctionalComponentLazy: {
642
- return updateFunctionalComponent(
699
+ child = updateFunctionalComponent(
700
current,
701
workInProgress,
702
Component,
703
resolvedProps,
704
renderExpirationTime,
705
);
706
+ break;
707
}
708
case ClassComponentLazy: {
651
- return updateClassComponent(
709
+ child = updateClassComponent(
710
current,
711
workInProgress,
712
Component,
713
resolvedProps,
714
renderExpirationTime,
715
);
716
+ break;
717
}
718
case ForwardRefLazy: {
660
- return updateForwardRef(
719
+ child = updateForwardRef(
720
current,
721
workInProgress,
722
Component,
723
resolvedProps,
724
renderExpirationTime,
725
);
726
+ break;
727
+ }
728
+ case PureComponentLazy: {
729
+ child = updatePureComponent(
730
+ current,
731
+ workInProgress,
732
+ Component,
733
+ resolvedProps,
734
+ updateExpirationTime,
735
+ renderExpirationTime,
736
+ );
737
+ break;
738
}
739
default: {
669
- // This message intentionally doesn't metion ForwardRef because the
670
- // fact that it's a separate type of work is an implementation detail.
740
+ // This message intentionally doesn't metion ForwardRef or PureComponent
741
+ // because the fact that it's a separate type of work is an
742
+ // implementation detail.
743
invariant(
744
false,
745
'Element type is invalid. Received a promise that resolves to: %s. ' +
@@ -676,6 +748,8 @@ function mountIndeterminateComponent(
748
);
749
}
750
}
751
+ workInProgress.memoizedProps = props;
752
+ return child;
753
}
754
755
const unmaskedContext = getUnmaskedContext(workInProgress, Component, false);
@@ -1106,59 +1180,65 @@ function beginWork(
1180
renderExpirationTime: ExpirationTime,
1181
): Fiber | null {
1182
const updateExpirationTime = workInProgress.expirationTime;
1109
- if (
1110
- !hasLegacyContextChanged() &&
1111
- (updateExpirationTime === NoWork ||
1112
- updateExpirationTime > renderExpirationTime)
1113
- ) {
1114
- // This fiber does not have any pending work. Bailout without entering
1115
- // the begin phase. There's still some bookkeeping we that needs to be done
1116
- // in this optimized path, mostly pushing stuff onto the stack.
1117
- switch (workInProgress.tag) {
1118
- case HostRoot:
1119
- pushHostRootContext(workInProgress);
1120
- resetHydrationState();
1121
- break;
1122
- case HostComponent:
1123
- pushHostContext(workInProgress);
1124
- break;
1125
- case ClassComponent: {
1126
- const Component = workInProgress.type;
1127
- if (isLegacyContextProvider(Component)) {
1128
- pushLegacyContextProvider(workInProgress);
1183
+
1184
+ if (current !== null) {
1185
+ const oldProps = current.memoizedProps;
1186
+ const newProps = workInProgress.pendingProps;
1187
+ if (
1188
+ oldProps === newProps &&
1189
+ !hasLegacyContextChanged() &&
1190
+ (updateExpirationTime === NoWork ||
1191
+ updateExpirationTime > renderExpirationTime)
1192
+ ) {
1193
+ // This fiber does not have any pending work. Bailout without entering
1194
+ // the begin phase. There's still some bookkeeping we that needs to be done
1195
+ // in this optimized path, mostly pushing stuff onto the stack.
1196
+ switch (workInProgress.tag) {
1197
+ case HostRoot:
1198
+ pushHostRootContext(workInProgress);
1199
+ resetHydrationState();
1200
+ break;
1201
+ case HostComponent:
1202
+ pushHostContext(workInProgress);
1203
+ break;
1204
+ case ClassComponent: {
1205
+ const Component = workInProgress.type;
1206
+ if (isLegacyContextProvider(Component)) {
1207
+ pushLegacyContextProvider(workInProgress);
1208
+ }
1209
+ break;
1210
}
1130
- break;
1131
- }
1132
- case ClassComponentLazy: {
1133
- const thenable = workInProgress.type;
1134
- const Component = getResultFromResolvedThenable(thenable);
1135
- if (isLegacyContextProvider(Component)) {
1136
- pushLegacyContextProvider(workInProgress);
1211
+ case ClassComponentLazy: {
1212
+ const thenable = workInProgress.type;
1213
+ const Component = getResultFromResolvedThenable(thenable);
1214
+ if (isLegacyContextProvider(Component)) {
1215
+ pushLegacyContextProvider(workInProgress);
1216
+ }
1217
+ break;
1218
}
1138
- break;
1139
- }
1140
- case HostPortal:
1141
- pushHostContainer(
1142
- workInProgress,
1143
- workInProgress.stateNode.containerInfo,
1144
- );
1145
- break;
1146
- case ContextProvider: {
1147
- const newValue = workInProgress.memoizedProps.value;
1148
- pushProvider(workInProgress, newValue);
1149
- break;
1150
- }
1151
- case Profiler:
1152
- if (enableProfilerTimer) {
1153
- workInProgress.effectTag |= Update;
1219
+ case HostPortal:
1220
+ pushHostContainer(
1221
+ workInProgress,
1222
+ workInProgress.stateNode.containerInfo,
1223
+ );
1224
+ break;
1225
+ case ContextProvider: {
1226
+ const newValue = workInProgress.memoizedProps.value;
1227
+ pushProvider(workInProgress, newValue);
1228
+ break;
1229
}
1155
- break;
1230
+ case Profiler:
1231
+ if (enableProfilerTimer) {
1232
+ workInProgress.effectTag |= Update;
1233
+ }
1234
+ break;
1235
+ }
1236
+ return bailoutOnAlreadyFinishedWork(
1237
+ current,
1238
+ workInProgress,
1239
+ renderExpirationTime,
1240
+ );
1241
}
1157
- return bailoutOnAlreadyFinishedWork(
1158
- current,
1159
- workInProgress,
1160
- renderExpirationTime,
1161
- );
1242
}
1243
1244
// Before entering the begin phase, clear the expiration time.
@@ -1171,6 +1251,7 @@ function beginWork(
1251
current,
1252
workInProgress,
1253
Component,
1254
+ updateExpirationTime,
1255
renderExpirationTime,
1256
);
1257
}
@@ -1252,7 +1333,7 @@ function beginWork(
1333
renderExpirationTime,
1334
);
1335
}
1255
- case ForwardRefLazy:
1336
+ case ForwardRefLazy: {
1337
const thenable = workInProgress.type;
1338
const Component = getResultFromResolvedThenable(thenable);
1339
const unresolvedProps = workInProgress.pendingProps;
@@ -1265,6 +1346,7 @@ function beginWork(
1346
);
1347
workInProgress.memoizedProps = unresolvedProps;
1348
return child;
1349
+ }
1350
case Fragment:
1351
return updateFragment(current, workInProgress, renderExpirationTime);
1352
case Mode:
@@ -1283,6 +1365,32 @@ function beginWork(
1365
workInProgress,
1366
renderExpirationTime,
1367
);
1368
+ case PureComponent: {
1369
+ const type = workInProgress.type;
1370
+ return updatePureComponent(
1371
+ current,
1372
+ workInProgress,
1373
+ type,
1374
+ workInProgress.pendingProps,
1375
+ updateExpirationTime,
1376
+ renderExpirationTime,
1377
+ );
1378
+ }
1379
+ case PureComponentLazy: {
1380
+ const thenable = workInProgress.type;
1381
+ const Component = getResultFromResolvedThenable(thenable);
1382
+ const unresolvedProps = workInProgress.pendingProps;
1383
+ const child = updatePureComponent(
1384
+ current,
1385
+ workInProgress,
1386
+ Component,
1387
+ resolveDefaultProps(Component, unresolvedProps),
1388
+ updateExpirationTime,
1389
+ renderExpirationTime,
1390
+ );
1391
+ workInProgress.memoizedProps = unresolvedProps;
1392
+ return child;
1393
+ }
1394
default:
1395
invariant(
1396
false,
packages/react-reconciler/src/ReactFiberCompleteWork.js
+5
@@ -36,6 +36,8 @@ import {
36
Profiler,
37
PlaceholderComponent,
38
ForwardRefLazy,
39
+ PureComponent,
40
+ PureComponentLazy,
41
} from 'shared/ReactWorkTags';
42
import {Placement, Ref, Update} from 'shared/ReactSideEffectTags';
43
import invariant from 'shared/invariant';
@@ -524,6 +526,9 @@ function completeWork(
526
break;
527
case ContextConsumer:
528
break;
529
+ case PureComponent:
530
+ case PureComponentLazy:
531
+ break;
532
// Error cases
533
case IndeterminateComponent:
534
invariant(
packages/react-reconciler/src/__tests__/ReactPure-test.internal.js
new
+194
@@ -0,0 +1,194 @@
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
+ * @emails react-core
8
+ * @jest-environment node
9
+ */
10
+
11
+/* eslint-disable no-func-assign */
12
+
13
+'use strict';
14
+
15
+let React;
16
+let ReactFeatureFlags;
17
+let ReactNoop;
18
+
19
+describe('pure', () => {
20
+ beforeEach(() => {
21
+ jest.resetModules();
22
+ ReactFeatureFlags = require('shared/ReactFeatureFlags');
23
+ ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
24
+ ReactFeatureFlags.enableSuspense = true;
25
+ React = require('react');
26
+ ReactNoop = require('react-noop-renderer');
27
+ });
28
+
29
+ function span(prop) {
30
+ return {type: 'span', children: [], prop};
31
+ }
32
+
33
+ function Text(props) {
34
+ ReactNoop.yield(props.text);
35
+ return <span prop={props.text} />;
36
+ }
37
+
38
+ // Tests should run against both the lazy and non-lazy versions of `pure`.
39
+ // To make the tests work for both versions, we wrap the non-lazy verion in
40
+ // a lazy function component.
41
+ sharedTests('normal', (...args) => {
42
+ const Pure = React.pure(...args);
43
+ function Indirection(props) {
44
+ return <Pure {...props} />;
45
+ }
46
+ return Promise.resolve(Indirection);
47
+ });
48
+ sharedTests('lazy', (...args) => Promise.resolve(React.pure(...args)));
49
+
50
+ function sharedTests(label, pure) {
51
+ describe(`${label}`, () => {
52
+ it('bails out on props equality', async () => {
53
+ const {Placeholder} = React;
54
+
55
+ function Counter({count}) {
56
+ return <Text text={count} />;
57
+ }
58
+ Counter = pure(Counter);
59
+
60
+ ReactNoop.render(
61
+ <Placeholder>
62
+ <Counter count={0} />
63
+ </Placeholder>,
64
+ );
65
+ expect(ReactNoop.flush()).toEqual([]);
66
+ await Promise.resolve();
67
+ expect(ReactNoop.flush()).toEqual([0]);
68
+ expect(ReactNoop.getChildren()).toEqual([span(0)]);
69
+
70
+ // Should bail out because props have not changed
71
+ ReactNoop.render(
72
+ <Placeholder>
73
+ <Counter count={0} />
74
+ </Placeholder>,
75
+ );
76
+ expect(ReactNoop.flush()).toEqual([]);
77
+ expect(ReactNoop.getChildren()).toEqual([span(0)]);
78
+
79
+ // Should update because count prop changed
80
+ ReactNoop.render(
81
+ <Placeholder>
82
+ <Counter count={1} />
83
+ </Placeholder>,
84
+ );
85
+ expect(ReactNoop.flush()).toEqual([1]);
86
+ expect(ReactNoop.getChildren()).toEqual([span(1)]);
87
+ });
88
+ });
89
+
90
+ it("does not bail out if there's a context change", async () => {
91
+ const {Placeholder} = React;
92
+
93
+ const CountContext = React.createContext(0);
94
+
95
+ function Counter(props) {
96
+ const count = CountContext.unstable_read();
97
+ return <Text text={`${props.label}: ${count}`} />;
98
+ }
99
+ Counter = pure(Counter);
100
+
101
+ class Parent extends React.Component {
102
+ state = {count: 0};
103
+ render() {
104
+ return (
105
+ <Placeholder>
106
+ <CountContext.Provider value={this.state.count}>
107
+ <Counter label="Count" />
108
+ </CountContext.Provider>
109
+ </Placeholder>
110
+ );
111
+ }
112
+ }
113
+
114
+ const parent = React.createRef(null);
115
+ ReactNoop.render(<Parent ref={parent} />);
116
+ expect(ReactNoop.flush()).toEqual([]);
117
+ await Promise.resolve();
118
+ expect(ReactNoop.flush()).toEqual(['Count: 0']);
119
+ expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);
120
+
121
+ // Should bail out because props have not changed
122
+ ReactNoop.render(<Parent ref={parent} />);
123
+ expect(ReactNoop.flush()).toEqual([]);
124
+ expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);
125
+
126
+ // Should update because there was a context change
127
+ parent.current.setState({count: 1});
128
+ expect(ReactNoop.flush()).toEqual(['Count: 1']);
129
+ expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
130
+ });
131
+
132
+ it('accepts custom comparison function', async () => {
133
+ const {Placeholder} = React;
134
+
135
+ function Counter({count}) {
136
+ return <Text text={count} />;
137
+ }
138
+ Counter = pure(Counter, (oldProps, newProps) => {
139
+ ReactNoop.yield(
140
+ `Old count: ${oldProps.count}, New count: ${newProps.count}`,
141
+ );
142
+ return oldProps.count === newProps.count;
143
+ });
144
+
145
+ ReactNoop.render(
146
+ <Placeholder>
147
+ <Counter count={0} />
148
+ </Placeholder>,
149
+ );
150
+ expect(ReactNoop.flush()).toEqual([]);
151
+ await Promise.resolve();
152
+ expect(ReactNoop.flush()).toEqual([0]);
153
+ expect(ReactNoop.getChildren()).toEqual([span(0)]);
154
+
155
+ // Should bail out because props have not changed
156
+ ReactNoop.render(
157
+ <Placeholder>
158
+ <Counter count={0} />
159
+ </Placeholder>,
160
+ );
161
+ expect(ReactNoop.flush()).toEqual(['Old count: 0, New count: 0']);
162
+ expect(ReactNoop.getChildren()).toEqual([span(0)]);
163
+
164
+ // Should update because count prop changed
165
+ ReactNoop.render(
166
+ <Placeholder>
167
+ <Counter count={1} />
168
+ </Placeholder>,
169
+ );
170
+ expect(ReactNoop.flush()).toEqual(['Old count: 0, New count: 1', 1]);
171
+ expect(ReactNoop.getChildren()).toEqual([span(1)]);
172
+ });
173
+
174
+ it('warns for class components', () => {
175
+ class SomeClass extends React.Component {
176
+ render() {
177
+ return null;
178
+ }
179
+ }
180
+ expect(() => pure(SomeClass)).toWarnDev(
181
+ 'pure: The first argument must be a functional component.',
182
+ {withoutStack: true},
183
+ );
184
+ });
185
+
186
+ it('warns if first argument is not a function', () => {
187
+ expect(() => pure()).toWarnDev(
188
+ 'pure: The first argument must be a functional component. Instead ' +
189
+ 'received: undefined',
190
+ {withoutStack: true},
191
+ );
192
+ });
193
+ }
194
+});
packages/react-test-renderer/src/ReactTestRenderer.js
+6
@@ -30,6 +30,8 @@ import {
30
ForwardRef,
31
Profiler,
32
ForwardRefLazy,
33
+ PureComponent,
34
+ PureComponentLazy,
35
} from 'shared/ReactWorkTags';
36
import invariant from 'shared/invariant';
37
import ReactVersion from 'shared/ReactVersion';
@@ -199,6 +201,8 @@ function toTree(node: ?Fiber) {
201
case Profiler:
202
case ForwardRef:
203
case ForwardRefLazy:
204
+ case PureComponent:
205
+ case PureComponentLazy:
206
return childrenToTree(node.child);
207
default:
208
invariant(
@@ -217,6 +221,8 @@ const validWrapperTypes = new Set([
221
HostComponent,
222
ForwardRef,
223
ForwardRefLazy,
224
+ PureComponent,
225
+ PureComponentLazy,
226
// Normally skipped, but used when there's more than one root child.
227
HostRoot,
228
]);
packages/react/src/React.js
+2
@@ -27,6 +27,7 @@ import {
27
import {createContext} from './ReactContext';
28
import {lazy} from './ReactLazy';
29
import forwardRef from './forwardRef';
30
+import pure from './pure';
31
import {
32
createElementWithValidation,
33
createFactoryWithValidation,
@@ -49,6 +50,7 @@ const React = {
50
51
createContext,
52
forwardRef,
53
+ pure,
54
55
Fragment: REACT_FRAGMENT_TYPE,
56
StrictMode: REACT_STRICT_MODE_TYPE,
packages/react/src/pure.js
new
+40
@@ -0,0 +1,40 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+import {REACT_PURE_TYPE} from 'shared/ReactSymbols';
9
+
10
+import warningWithoutStack from 'shared/warningWithoutStack';
11
+
12
+export default function pure<Props>(
13
+ render: (props: Props) => React$Node,
14
+ compare?: (oldProps: Props, newProps: Props) => boolean,
15
+) {
16
+ if (__DEV__) {
17
+ if (typeof render !== 'function') {
18
+ warningWithoutStack(
19
+ false,
20
+ 'pure: The first argument must be a functional component. Instead ' +
21
+ 'received: %s',
22
+ render === null ? 'null' : typeof render,
23
+ );
24
+ } else {
25
+ const prototype = render.prototype;
26
+ if (prototype && prototype.isReactComponent) {
27
+ warningWithoutStack(
28
+ false,
29
+ 'pure: The first argument must be a functional component. Classes ' +
30
+ 'are not supported. Use React.PureComponent instead.',
31
+ );
32
+ }
33
+ }
34
+ }
35
+ return {
36
+ $$typeof: REACT_PURE_TYPE,
37
+ render,
38
+ compare: compare === undefined ? null : compare,
39
+ };
40
+}
packages/shared/ReactSymbols.js
+1
@@ -41,6 +41,7 @@ export const REACT_FORWARD_REF_TYPE = hasSymbol
41
export const REACT_PLACEHOLDER_TYPE = hasSymbol
42
? Symbol.for('react.placeholder')
43
: 0xead1;
44
+export const REACT_PURE_TYPE = hasSymbol ? Symbol.for('react.pure') : 0xead3;
45
46
const MAYBE_ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;
47
const FAUX_ITERATOR_SYMBOL = '@@iterator';
packages/shared/ReactWorkTags.js
+5
-1
@@ -24,7 +24,9 @@ export type WorkTag =
24
| 13
25
| 14
26
| 15
27
- | 16;
27
+ | 16
28
+ | 17
29
+ | 18;
30
31
export const FunctionalComponent = 0;
32
export const FunctionalComponentLazy = 1;
@@ -43,3 +45,5 @@ export const ForwardRef = 13;
45
export const ForwardRefLazy = 14;
46
export const Profiler = 15;
47
export const PlaceholderComponent = 16;
48
+export const PureComponent = 17;
49
+export const PureComponentLazy = 18;
packages/shared/isValidElementType.js
+2
@@ -16,6 +16,7 @@ import {
16
REACT_PROVIDER_TYPE,
17
REACT_STRICT_MODE_TYPE,
18
REACT_PLACEHOLDER_TYPE,
19
+ REACT_PURE_TYPE,
20
} from 'shared/ReactSymbols';
21
22
export default function isValidElementType(type: mixed) {
@@ -31,6 +32,7 @@ export default function isValidElementType(type: mixed) {
32
(typeof type === 'object' &&
33
type !== null &&
34
(typeof type.then === 'function' ||
35
+ type.$$typeof === REACT_PURE_TYPE ||
36
type.$$typeof === REACT_PROVIDER_TYPE ||
37
type.$$typeof === REACT_CONTEXT_TYPE ||
38
type.$$typeof === REACT_FORWARD_REF_TYPE))