@samitouri / QOS-React-2 / commits / a0733fe13d

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))