Skip to content

Commit 726d7d8

Browse files
committed
Revert "Remove Numeric Fallback of Symbols (facebook#23348)"
This reverts commit 587e759.
1 parent 645ec5d commit 726d7d8

File tree

5 files changed

+203
-36
lines changed

5 files changed

+203
-36
lines changed

packages/react/src/__tests__/ReactElement-test.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,16 @@ let ReactTestUtils;
1515

1616
describe('ReactElement', () => {
1717
let ComponentClass;
18+
let originalSymbol;
1819

1920
beforeEach(() => {
2021
jest.resetModules();
2122

23+
// Delete the native Symbol if we have one to ensure we test the
24+
// unpolyfilled environment.
25+
originalSymbol = global.Symbol;
26+
global.Symbol = undefined;
27+
2228
React = require('react');
2329
ReactDOM = require('react-dom');
2430
ReactTestUtils = require('react-dom/test-utils');
@@ -31,6 +37,14 @@ describe('ReactElement', () => {
3137
};
3238
});
3339

40+
afterEach(() => {
41+
global.Symbol = originalSymbol;
42+
});
43+
44+
it('uses the fallback value when in an environment without Symbol', () => {
45+
expect((<div />).$$typeof).toBe(0xeac7);
46+
});
47+
3448
it('returns a complete element according to spec', () => {
3549
const element = React.createElement(ComponentClass);
3650
expect(element.type).toBe(ComponentClass);
@@ -280,6 +294,41 @@ describe('ReactElement', () => {
280294
expect(element.type.someStaticMethod()).toBe('someReturnValue');
281295
});
282296

297+
// NOTE: We're explicitly not using JSX here. This is intended to test
298+
// classic JS without JSX.
299+
it('identifies valid elements', () => {
300+
class Component extends React.Component {
301+
render() {
302+
return React.createElement('div');
303+
}
304+
}
305+
306+
expect(React.isValidElement(React.createElement('div'))).toEqual(true);
307+
expect(React.isValidElement(React.createElement(Component))).toEqual(true);
308+
309+
expect(React.isValidElement(null)).toEqual(false);
310+
expect(React.isValidElement(true)).toEqual(false);
311+
expect(React.isValidElement({})).toEqual(false);
312+
expect(React.isValidElement('string')).toEqual(false);
313+
if (!__EXPERIMENTAL__) {
314+
let factory;
315+
expect(() => {
316+
factory = React.createFactory('div');
317+
}).toWarnDev(
318+
'Warning: React.createFactory() is deprecated and will be removed in a ' +
319+
'future major release. Consider using JSX or use React.createElement() ' +
320+
'directly instead.',
321+
{withoutStack: true},
322+
);
323+
expect(React.isValidElement(factory)).toEqual(false);
324+
}
325+
expect(React.isValidElement(Component)).toEqual(false);
326+
expect(React.isValidElement({type: 'div', props: {}})).toEqual(false);
327+
328+
const jsonElement = JSON.stringify(React.createElement('div'));
329+
expect(React.isValidElement(JSON.parse(jsonElement))).toBe(true);
330+
});
331+
283332
// NOTE: We're explicitly not using JSX here. This is intended to test
284333
// classic JS without JSX.
285334
it('is indistinguishable from a plain object', () => {
@@ -398,6 +447,25 @@ describe('ReactElement', () => {
398447
// NOTE: We're explicitly not using JSX here. This is intended to test
399448
// classic JS without JSX.
400449
it('identifies elements, but not JSON, if Symbols are supported', () => {
450+
// Rudimentary polyfill
451+
// Once all jest engines support Symbols natively we can swap this to test
452+
// WITH native Symbols by default.
453+
const REACT_ELEMENT_TYPE = function() {}; // fake Symbol
454+
const OTHER_SYMBOL = function() {}; // another fake Symbol
455+
global.Symbol = function(name) {
456+
return OTHER_SYMBOL;
457+
};
458+
global.Symbol.for = function(key) {
459+
if (key === 'react.element') {
460+
return REACT_ELEMENT_TYPE;
461+
}
462+
return OTHER_SYMBOL;
463+
};
464+
465+
jest.resetModules();
466+
467+
React = require('react');
468+
401469
class Component extends React.Component {
402470
render() {
403471
return React.createElement('div');

packages/react/src/__tests__/ReactElementJSX-test.js

Lines changed: 73 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,27 @@ let JSXDEVRuntime;
2020
// A lot of these tests are pulled from ReactElement-test because
2121
// this api is meant to be backwards compatible.
2222
describe('ReactElement.jsx', () => {
23+
let originalSymbol;
24+
2325
beforeEach(() => {
2426
jest.resetModules();
2527

28+
// Delete the native Symbol if we have one to ensure we test the
29+
// unpolyfilled environment.
30+
originalSymbol = global.Symbol;
31+
global.Symbol = undefined;
32+
2633
React = require('react');
2734
JSXRuntime = require('react/jsx-runtime');
2835
JSXDEVRuntime = require('react/jsx-dev-runtime');
2936
ReactDOM = require('react-dom');
3037
ReactTestUtils = require('react-dom/test-utils');
3138
});
3239

40+
afterEach(() => {
41+
global.Symbol = originalSymbol;
42+
});
43+
3344
it('allows static methods to be called using the type property', () => {
3445
class StaticMethodComponentClass extends React.Component {
3546
render() {
@@ -42,6 +53,47 @@ describe('ReactElement.jsx', () => {
4253
expect(element.type.someStaticMethod()).toBe('someReturnValue');
4354
});
4455

56+
it('identifies valid elements', () => {
57+
class Component extends React.Component {
58+
render() {
59+
return JSXRuntime.jsx('div', {});
60+
}
61+
}
62+
63+
expect(React.isValidElement(JSXRuntime.jsx('div', {}))).toEqual(true);
64+
expect(React.isValidElement(JSXRuntime.jsx(Component, {}))).toEqual(true);
65+
expect(
66+
React.isValidElement(JSXRuntime.jsx(JSXRuntime.Fragment, {})),
67+
).toEqual(true);
68+
if (__DEV__) {
69+
expect(React.isValidElement(JSXDEVRuntime.jsxDEV('div', {}))).toEqual(
70+
true,
71+
);
72+
}
73+
74+
expect(React.isValidElement(null)).toEqual(false);
75+
expect(React.isValidElement(true)).toEqual(false);
76+
expect(React.isValidElement({})).toEqual(false);
77+
expect(React.isValidElement('string')).toEqual(false);
78+
if (!__EXPERIMENTAL__) {
79+
let factory;
80+
expect(() => {
81+
factory = React.createFactory('div');
82+
}).toWarnDev(
83+
'Warning: React.createFactory() is deprecated and will be removed in a ' +
84+
'future major release. Consider using JSX or use React.createElement() ' +
85+
'directly instead.',
86+
{withoutStack: true},
87+
);
88+
expect(React.isValidElement(factory)).toEqual(false);
89+
}
90+
expect(React.isValidElement(Component)).toEqual(false);
91+
expect(React.isValidElement({type: 'div', props: {}})).toEqual(false);
92+
93+
const jsonElement = JSON.stringify(JSXRuntime.jsx('div', {}));
94+
expect(React.isValidElement(JSON.parse(jsonElement))).toBe(true);
95+
});
96+
4597
it('is indistinguishable from a plain object', () => {
4698
const element = JSXRuntime.jsx('div', {className: 'foo'});
4799
const object = {};
@@ -236,22 +288,34 @@ describe('ReactElement.jsx', () => {
236288
});
237289

238290
it('identifies elements, but not JSON, if Symbols are supported', () => {
291+
// Rudimentary polyfill
292+
// Once all jest engines support Symbols natively we can swap this to test
293+
// WITH native Symbols by default.
294+
const REACT_ELEMENT_TYPE = function() {}; // fake Symbol
295+
const OTHER_SYMBOL = function() {}; // another fake Symbol
296+
global.Symbol = function(name) {
297+
return OTHER_SYMBOL;
298+
};
299+
global.Symbol.for = function(key) {
300+
if (key === 'react.element') {
301+
return REACT_ELEMENT_TYPE;
302+
}
303+
return OTHER_SYMBOL;
304+
};
305+
306+
jest.resetModules();
307+
308+
React = require('react');
309+
JSXRuntime = require('react/jsx-runtime');
310+
239311
class Component extends React.Component {
240312
render() {
241-
return JSXRuntime.jsx('div', {});
313+
return JSXRuntime.jsx('div');
242314
}
243315
}
244316

245317
expect(React.isValidElement(JSXRuntime.jsx('div', {}))).toEqual(true);
246318
expect(React.isValidElement(JSXRuntime.jsx(Component, {}))).toEqual(true);
247-
expect(
248-
React.isValidElement(JSXRuntime.jsx(JSXRuntime.Fragment, {})),
249-
).toEqual(true);
250-
if (__DEV__) {
251-
expect(React.isValidElement(JSXDEVRuntime.jsxDEV('div', {}))).toEqual(
252-
true,
253-
);
254-
}
255319

256320
expect(React.isValidElement(null)).toEqual(false);
257321
expect(React.isValidElement(true)).toEqual(false);

packages/shared/ReactSymbols.js

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,33 +11,50 @@
1111
// When adding new symbols to this file,
1212
// Please consider also adding to 'react-devtools-shared/src/backend/ReactSymbols'
1313

14-
// The Symbol used to tag the ReactElement-like types.
15-
export const REACT_ELEMENT_TYPE = Symbol.for('react.element');
16-
export const REACT_PORTAL_TYPE = Symbol.for('react.portal');
17-
export const REACT_FRAGMENT_TYPE = Symbol.for('react.fragment');
18-
export const REACT_STRICT_MODE_TYPE = Symbol.for('react.strict_mode');
19-
export const REACT_PROFILER_TYPE = Symbol.for('react.profiler');
20-
export const REACT_PROVIDER_TYPE = Symbol.for('react.provider');
21-
export const REACT_CONTEXT_TYPE = Symbol.for('react.context');
22-
export const REACT_SERVER_CONTEXT_TYPE = Symbol.for('react.server_context');
23-
export const REACT_FORWARD_REF_TYPE = Symbol.for('react.forward_ref');
24-
export const REACT_SUSPENSE_TYPE = Symbol.for('react.suspense');
25-
export const REACT_SUSPENSE_LIST_TYPE = Symbol.for('react.suspense_list');
26-
export const REACT_MEMO_TYPE = Symbol.for('react.memo');
27-
export const REACT_LAZY_TYPE = Symbol.for('react.lazy');
28-
export const REACT_SCOPE_TYPE = Symbol.for('react.scope');
29-
export const REACT_DEBUG_TRACING_MODE_TYPE = Symbol.for(
30-
'react.debug_trace_mode',
31-
);
32-
export const REACT_OFFSCREEN_TYPE = Symbol.for('react.offscreen');
33-
export const REACT_LEGACY_HIDDEN_TYPE = Symbol.for('react.legacy_hidden');
34-
export const REACT_CACHE_TYPE = Symbol.for('react.cache');
35-
export const REACT_TRACING_MARKER_TYPE = Symbol.for('react.tracing_marker');
36-
export const REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED = Symbol.for(
37-
'react.default_value',
38-
);
14+
// The Symbol used to tag the ReactElement-like types. If there is no native Symbol
15+
// nor polyfill, then a plain number is used for performance.
16+
export let REACT_ELEMENT_TYPE = 0xeac7;
17+
export let REACT_PORTAL_TYPE = 0xeaca;
18+
export let REACT_FRAGMENT_TYPE = 0xeacb;
19+
export let REACT_STRICT_MODE_TYPE = 0xeacc;
20+
export let REACT_PROFILER_TYPE = 0xead2;
21+
export let REACT_PROVIDER_TYPE = 0xeacd;
22+
export let REACT_CONTEXT_TYPE = 0xeace;
23+
export let REACT_FORWARD_REF_TYPE = 0xead0;
24+
export let REACT_SUSPENSE_TYPE = 0xead1;
25+
export let REACT_SUSPENSE_LIST_TYPE = 0xead8;
26+
export let REACT_MEMO_TYPE = 0xead3;
27+
export let REACT_LAZY_TYPE = 0xead4;
28+
export let REACT_SCOPE_TYPE = 0xead7;
29+
export let REACT_DEBUG_TRACING_MODE_TYPE = 0xeae1;
30+
export let REACT_OFFSCREEN_TYPE = 0xeae2;
31+
export let REACT_LEGACY_HIDDEN_TYPE = 0xeae3;
32+
export let REACT_CACHE_TYPE = 0xeae4;
33+
export let REACT_TRACING_MARKER_TYPE = 0xeae5;
3934

40-
const MAYBE_ITERATOR_SYMBOL = Symbol.iterator;
35+
if (typeof Symbol === 'function' && Symbol.for) {
36+
const symbolFor = Symbol.for;
37+
REACT_ELEMENT_TYPE = symbolFor('react.element');
38+
REACT_PORTAL_TYPE = symbolFor('react.portal');
39+
REACT_FRAGMENT_TYPE = symbolFor('react.fragment');
40+
REACT_STRICT_MODE_TYPE = symbolFor('react.strict_mode');
41+
REACT_PROFILER_TYPE = symbolFor('react.profiler');
42+
REACT_PROVIDER_TYPE = symbolFor('react.provider');
43+
REACT_CONTEXT_TYPE = symbolFor('react.context');
44+
REACT_FORWARD_REF_TYPE = symbolFor('react.forward_ref');
45+
REACT_SUSPENSE_TYPE = symbolFor('react.suspense');
46+
REACT_SUSPENSE_LIST_TYPE = symbolFor('react.suspense_list');
47+
REACT_MEMO_TYPE = symbolFor('react.memo');
48+
REACT_LAZY_TYPE = symbolFor('react.lazy');
49+
REACT_SCOPE_TYPE = symbolFor('react.scope');
50+
REACT_DEBUG_TRACING_MODE_TYPE = symbolFor('react.debug_trace_mode');
51+
REACT_OFFSCREEN_TYPE = symbolFor('react.offscreen');
52+
REACT_LEGACY_HIDDEN_TYPE = symbolFor('react.legacy_hidden');
53+
REACT_CACHE_TYPE = symbolFor('react.cache');
54+
REACT_TRACING_MARKER_TYPE = symbolFor('react.tracing_marker');
55+
}
56+
57+
const MAYBE_ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;
4158
const FAUX_ITERATOR_SYMBOL = '@@iterator';
4259

4360
export function getIteratorFn(maybeIterable: ?any): ?() => ?Iterator<*> {

packages/shared/__tests__/ReactSymbols-test.internal.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,19 @@ describe('ReactSymbols', () => {
2626
it('Symbol values should be unique', () => {
2727
expectToBeUnique(Object.entries(require('shared/ReactSymbols')));
2828
});
29+
30+
it('numeric values should be unique', () => {
31+
const originalSymbolFor = global.Symbol.for;
32+
global.Symbol.for = null;
33+
try {
34+
const entries = Object.entries(require('shared/ReactSymbols')).filter(
35+
// REACT_ASYNC_MODE_TYPE and REACT_CONCURRENT_MODE_TYPE have the same numeric value
36+
// for legacy backwards compatibility
37+
([key]) => key !== 'REACT_ASYNC_MODE_TYPE',
38+
);
39+
expectToBeUnique(entries);
40+
} finally {
41+
global.Symbol.for = originalSymbolFor;
42+
}
43+
});
2944
});

packages/shared/isValidElementType.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ import {
3333
enableLegacyHidden,
3434
} from './ReactFeatureFlags';
3535

36-
const REACT_MODULE_REFERENCE: Symbol = Symbol.for('react.module.reference');
36+
let REACT_MODULE_REFERENCE: number | Symbol = 0;
37+
if (typeof Symbol === 'function') {
38+
REACT_MODULE_REFERENCE = Symbol.for('react.module.reference');
39+
}
3740

3841
export default function isValidElementType(type: mixed) {
3942
if (typeof type === 'string' || typeof type === 'function') {

0 commit comments

Comments
 (0)