mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 00:20:04 +01:00
React DevTools: Show symbols used as keys in state (#19786)
Co-authored-by: Brian Vaughn <bvaughn@fb.com>
This commit is contained in:
parent
11ee82df45
commit
917cb01a58
|
|
@ -208,6 +208,70 @@
|
|||
return <ChildComponent customObject={new Custom()} />;
|
||||
}
|
||||
|
||||
const baseInheritedKeys = Object.create(Object.prototype, {
|
||||
enumerableStringBase: {
|
||||
value: 1,
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
},
|
||||
[Symbol('enumerableSymbolBase')]: {
|
||||
value: 1,
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
},
|
||||
nonEnumerableStringBase: {
|
||||
value: 1,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
},
|
||||
[Symbol('nonEnumerableSymbolBase')]: {
|
||||
value: 1,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
},
|
||||
});
|
||||
|
||||
const inheritedKeys = Object.create(baseInheritedKeys, {
|
||||
enumerableString: {
|
||||
value: 2,
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
},
|
||||
nonEnumerableString: {
|
||||
value: 3,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
},
|
||||
123: {
|
||||
value: 3,
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
},
|
||||
[Symbol('nonEnumerableSymbol')]: {
|
||||
value: 2,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
},
|
||||
[Symbol('enumerableSymbol')]: {
|
||||
value: 3,
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
},
|
||||
});
|
||||
|
||||
function InheritedKeys() {
|
||||
return <ChildComponent data={inheritedKeys} />;
|
||||
}
|
||||
|
||||
const object = {
|
||||
string: "abc",
|
||||
longString: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKJLMNOPQRSTUVWXYZ1234567890",
|
||||
|
|
@ -294,6 +358,7 @@
|
|||
<ObjectProps />
|
||||
<UnserializableProps />
|
||||
<CustomObject />
|
||||
<InheritedKeys />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -541,6 +541,9 @@ exports[`InspectedElementContext should support complex data types: 1: Inspected
|
|||
"object_of_objects": {
|
||||
"inner": {}
|
||||
},
|
||||
"object_with_symbol": {
|
||||
"Symbol(name)": "hello"
|
||||
},
|
||||
"proxy": {},
|
||||
"react_element": {},
|
||||
"regexp": {},
|
||||
|
|
@ -612,6 +615,25 @@ exports[`InspectedElementContext should support objects with overridden hasOwnPr
|
|||
}
|
||||
`;
|
||||
|
||||
exports[`InspectedElementContext should support objects with with inherited keys: 1: Inspected element 2 1`] = `
|
||||
{
|
||||
"id": 2,
|
||||
"owners": null,
|
||||
"context": null,
|
||||
"hooks": null,
|
||||
"props": {
|
||||
"object": {
|
||||
"123": 3,
|
||||
"enumerableString": 2,
|
||||
"Symbol(enumerableSymbol)": 3,
|
||||
"enumerableStringBase": 1,
|
||||
"Symbol(enumerableSymbolBase)": 1
|
||||
}
|
||||
},
|
||||
"state": null
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`InspectedElementContext should support simple data types: 1: Initial inspection 1`] = `
|
||||
{
|
||||
"id": 2,
|
||||
|
|
|
|||
|
|
@ -537,6 +537,9 @@ describe('InspectedElementContext', () => {
|
|||
const objectOfObjects = {
|
||||
inner: {string: 'abc', number: 123, boolean: true},
|
||||
};
|
||||
const objectWithSymbol = {
|
||||
[Symbol('name')]: 'hello',
|
||||
};
|
||||
const typedArray = Int8Array.from([100, -100, 0]);
|
||||
const arrayBuffer = typedArray.buffer;
|
||||
const dataView = new DataView(arrayBuffer);
|
||||
|
|
@ -580,6 +583,7 @@ describe('InspectedElementContext', () => {
|
|||
map={mapShallow}
|
||||
map_of_maps={mapOfMaps}
|
||||
object_of_objects={objectOfObjects}
|
||||
object_with_symbol={objectWithSymbol}
|
||||
proxy={proxyInstance}
|
||||
react_element={<span />}
|
||||
regexp={/abc/giu}
|
||||
|
|
@ -633,6 +637,7 @@ describe('InspectedElementContext', () => {
|
|||
map,
|
||||
map_of_maps,
|
||||
object_of_objects,
|
||||
object_with_symbol,
|
||||
proxy,
|
||||
react_element,
|
||||
regexp,
|
||||
|
|
@ -737,6 +742,8 @@ describe('InspectedElementContext', () => {
|
|||
);
|
||||
expect(object_of_objects.inner[meta.preview_short]).toBe('{…}');
|
||||
|
||||
expect(object_with_symbol['Symbol(name)']).toBe('hello');
|
||||
|
||||
expect(proxy[meta.inspectable]).toBe(false);
|
||||
expect(proxy[meta.name]).toBe('function');
|
||||
expect(proxy[meta.type]).toBe('function');
|
||||
|
|
@ -939,6 +946,111 @@ describe('InspectedElementContext', () => {
|
|||
done();
|
||||
});
|
||||
|
||||
it('should support objects with with inherited keys', async done => {
|
||||
const Example = () => null;
|
||||
|
||||
const base = Object.create(Object.prototype, {
|
||||
enumerableStringBase: {
|
||||
value: 1,
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
},
|
||||
[Symbol('enumerableSymbolBase')]: {
|
||||
value: 1,
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
},
|
||||
nonEnumerableStringBase: {
|
||||
value: 1,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
},
|
||||
[Symbol('nonEnumerableSymbolBase')]: {
|
||||
value: 1,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
},
|
||||
});
|
||||
|
||||
const object = Object.create(base, {
|
||||
enumerableString: {
|
||||
value: 2,
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
},
|
||||
nonEnumerableString: {
|
||||
value: 3,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
},
|
||||
[123]: {
|
||||
value: 3,
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
},
|
||||
[Symbol('nonEnumerableSymbol')]: {
|
||||
value: 2,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
},
|
||||
[Symbol('enumerableSymbol')]: {
|
||||
value: 3,
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
},
|
||||
});
|
||||
|
||||
const container = document.createElement('div');
|
||||
await utils.actAsync(() =>
|
||||
ReactDOM.render(<Example object={object} />, container),
|
||||
);
|
||||
|
||||
const id = ((store.getElementIDAtIndex(0): any): number);
|
||||
|
||||
let inspectedElement = null;
|
||||
|
||||
function Suspender({target}) {
|
||||
const {getInspectedElement} = React.useContext(InspectedElementContext);
|
||||
inspectedElement = getInspectedElement(id);
|
||||
return null;
|
||||
}
|
||||
|
||||
await utils.actAsync(
|
||||
() =>
|
||||
TestRenderer.create(
|
||||
<Contexts
|
||||
defaultSelectedElementID={id}
|
||||
defaultSelectedElementIndex={0}>
|
||||
<React.Suspense fallback={null}>
|
||||
<Suspender target={id} />
|
||||
</React.Suspense>
|
||||
</Contexts>,
|
||||
),
|
||||
false,
|
||||
);
|
||||
|
||||
expect(inspectedElement).not.toBeNull();
|
||||
expect(inspectedElement).toMatchSnapshot(`1: Inspected element ${id}`);
|
||||
expect(inspectedElement.props.object).toEqual({
|
||||
123: 3,
|
||||
'Symbol(enumerableSymbol)': 3,
|
||||
'Symbol(enumerableSymbolBase)': 1,
|
||||
enumerableString: 2,
|
||||
enumerableStringBase: 1,
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should not dehydrate nested values until explicitly requested', async done => {
|
||||
const Example = () => {
|
||||
const [state] = React.useState({
|
||||
|
|
|
|||
|
|
@ -236,6 +236,29 @@ Object {
|
|||
}
|
||||
`;
|
||||
|
||||
exports[`InspectedElementContext should support objects with with inherited keys: 1: Initial inspection 1`] = `
|
||||
Object {
|
||||
"id": 2,
|
||||
"type": "full-data",
|
||||
"value": {
|
||||
"id": 2,
|
||||
"owners": null,
|
||||
"context": {},
|
||||
"hooks": null,
|
||||
"props": {
|
||||
"data": {
|
||||
"123": 3,
|
||||
"enumerableString": 2,
|
||||
"Symbol(enumerableSymbol)": 3,
|
||||
"enumerableStringBase": 1,
|
||||
"Symbol(enumerableSymbolBase)": 1
|
||||
}
|
||||
},
|
||||
"state": null
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`InspectedElementContext should support simple data types: 1: Initial inspection 1`] = `
|
||||
Object {
|
||||
"id": 2,
|
||||
|
|
|
|||
|
|
@ -432,6 +432,81 @@ describe('InspectedElementContext', () => {
|
|||
done();
|
||||
});
|
||||
|
||||
it('should support objects with with inherited keys', async done => {
|
||||
const Example = () => null;
|
||||
|
||||
const base = Object.create(Object.prototype, {
|
||||
enumerableStringBase: {
|
||||
value: 1,
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
},
|
||||
[Symbol('enumerableSymbolBase')]: {
|
||||
value: 1,
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
},
|
||||
nonEnumerableStringBase: {
|
||||
value: 1,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
},
|
||||
[Symbol('nonEnumerableSymbolBase')]: {
|
||||
value: 1,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
},
|
||||
});
|
||||
|
||||
const object = Object.create(base, {
|
||||
enumerableString: {
|
||||
value: 2,
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
},
|
||||
nonEnumerableString: {
|
||||
value: 3,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
},
|
||||
[123]: {
|
||||
value: 3,
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
},
|
||||
[Symbol('nonEnumerableSymbol')]: {
|
||||
value: 2,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
},
|
||||
[Symbol('enumerableSymbol')]: {
|
||||
value: 3,
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
},
|
||||
});
|
||||
|
||||
act(() =>
|
||||
ReactDOM.render(<Example data={object} />, document.createElement('div')),
|
||||
);
|
||||
|
||||
const id = ((store.getElementIDAtIndex(0): any): number);
|
||||
const inspectedElement = await read(id);
|
||||
|
||||
expect(inspectedElement).toMatchSnapshot('1: Initial inspection');
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should not dehydrate nested values until explicitly requested', async done => {
|
||||
const Example = () => null;
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
import {
|
||||
getDataType,
|
||||
getDisplayNameForReactElement,
|
||||
getAllEnumerableKeys,
|
||||
getInObject,
|
||||
formatDataForPreview,
|
||||
setInObject,
|
||||
|
|
@ -291,16 +292,17 @@ export function dehydrate(
|
|||
return createDehydrated(type, true, data, cleaned, path);
|
||||
} else {
|
||||
const object = {};
|
||||
for (const name in data) {
|
||||
getAllEnumerableKeys(data).forEach(key => {
|
||||
const name = key.toString();
|
||||
object[name] = dehydrate(
|
||||
data[name],
|
||||
data[key],
|
||||
cleaned,
|
||||
unserializable,
|
||||
path.concat([name]),
|
||||
isPathAllowed,
|
||||
isPathAllowedCheck ? 1 : level + 1,
|
||||
);
|
||||
}
|
||||
});
|
||||
return object;
|
||||
}
|
||||
|
||||
|
|
|
|||
38
packages/react-devtools-shared/src/utils.js
vendored
38
packages/react-devtools-shared/src/utils.js
vendored
|
|
@ -52,16 +52,41 @@ const cachedDisplayNames: WeakMap<Function, string> = new WeakMap();
|
|||
// Try to reuse the already encoded strings.
|
||||
const encodedStringCache = new LRU({max: 1000});
|
||||
|
||||
export function alphaSortKeys(a: string, b: string): number {
|
||||
if (a > b) {
|
||||
export function alphaSortKeys(
|
||||
a: string | number | Symbol,
|
||||
b: string | number | Symbol,
|
||||
): number {
|
||||
if (a.toString() > b.toString()) {
|
||||
return 1;
|
||||
} else if (b > a) {
|
||||
} else if (b.toString() > a.toString()) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
export function getAllEnumerableKeys(
|
||||
obj: Object,
|
||||
): Array<string | number | Symbol> {
|
||||
const keys = [];
|
||||
let current = obj;
|
||||
while (current != null) {
|
||||
const currentKeys = [
|
||||
...Object.keys(current),
|
||||
...Object.getOwnPropertySymbols(current),
|
||||
];
|
||||
const descriptors = Object.getOwnPropertyDescriptors(current);
|
||||
currentKeys.forEach(key => {
|
||||
// $FlowFixMe: key can be a Symbol https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor
|
||||
if (descriptors[key].enumerable) {
|
||||
keys.push(key);
|
||||
}
|
||||
});
|
||||
current = Object.getPrototypeOf(current);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
export function getDisplayName(
|
||||
type: Function,
|
||||
fallbackName: string = 'Anonymous',
|
||||
|
|
@ -657,7 +682,7 @@ export function formatDataForPreview(
|
|||
return data.toString();
|
||||
case 'object':
|
||||
if (showFormattedValue) {
|
||||
const keys = Object.keys(data).sort(alphaSortKeys);
|
||||
const keys = getAllEnumerableKeys(data).sort(alphaSortKeys);
|
||||
|
||||
let formatted = '';
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
|
|
@ -665,7 +690,10 @@ export function formatDataForPreview(
|
|||
if (i > 0) {
|
||||
formatted += ', ';
|
||||
}
|
||||
formatted += `${key}: ${formatDataForPreview(data[key], false)}`;
|
||||
formatted += `${key.toString()}: ${formatDataForPreview(
|
||||
data[key],
|
||||
false,
|
||||
)}`;
|
||||
if (formatted.length > MAX_PREVIEW_STRING_LENGTH) {
|
||||
// Prevent doing a lot of unnecessary iteration...
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import CustomObject from './CustomObject';
|
|||
import EdgeCaseObjects from './EdgeCaseObjects.js';
|
||||
import NestedProps from './NestedProps';
|
||||
import SimpleValues from './SimpleValues';
|
||||
import SymbolKeys from './SymbolKeys';
|
||||
|
||||
// TODO Add Immutable JS example
|
||||
|
||||
|
|
@ -32,6 +33,7 @@ export default function InspectableElements() {
|
|||
<CustomObject />
|
||||
<EdgeCaseObjects />
|
||||
<CircularReferences />
|
||||
<SymbolKeys />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
78
packages/react-devtools-shell/src/app/InspectableElements/SymbolKeys.js
vendored
Normal file
78
packages/react-devtools-shell/src/app/InspectableElements/SymbolKeys.js
vendored
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
const base = Object.create(Object.prototype, {
|
||||
enumerableStringBase: {
|
||||
value: 1,
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
},
|
||||
[Symbol('enumerableSymbolBase')]: {
|
||||
value: 1,
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
},
|
||||
nonEnumerableStringBase: {
|
||||
value: 1,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
},
|
||||
[Symbol('nonEnumerableSymbolBase')]: {
|
||||
value: 1,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
},
|
||||
});
|
||||
|
||||
const data = Object.create(base, {
|
||||
enumerableString: {
|
||||
value: 2,
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
},
|
||||
nonEnumerableString: {
|
||||
value: 3,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
},
|
||||
[123]: {
|
||||
value: 3,
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
},
|
||||
[Symbol('nonEnumerableSymbol')]: {
|
||||
value: 2,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
},
|
||||
[Symbol('enumerableSymbol')]: {
|
||||
value: 3,
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
},
|
||||
});
|
||||
|
||||
export default function SymbolKeys() {
|
||||
return <ChildComponent data={data} />;
|
||||
}
|
||||
|
||||
function ChildComponent(props: any) {
|
||||
return null;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user