mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
[Fiber] Bail out of diffing wide objects and arrays (#34742)
This commit is contained in:
parent
3b2a398106
commit
1be3ce9996
327
packages/react-reconciler/src/__tests__/ReactPerformanceTrack-test.js
vendored
Normal file
327
packages/react-reconciler/src/__tests__/ReactPerformanceTrack-test.js
vendored
Normal file
|
|
@ -0,0 +1,327 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @emails react-core
|
||||||
|
* @jest-environment node
|
||||||
|
*/
|
||||||
|
|
||||||
|
let React;
|
||||||
|
let ReactNoop;
|
||||||
|
let Scheduler;
|
||||||
|
let act;
|
||||||
|
let useEffect;
|
||||||
|
|
||||||
|
describe('ReactPerformanceTracks', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
Object.defineProperty(performance, 'measure', {
|
||||||
|
value: jest.fn(),
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
console.timeStamp = () => {};
|
||||||
|
jest.spyOn(console, 'timeStamp').mockImplementation(() => {});
|
||||||
|
|
||||||
|
jest.resetModules();
|
||||||
|
|
||||||
|
React = require('react');
|
||||||
|
ReactNoop = require('react-noop-renderer');
|
||||||
|
Scheduler = require('scheduler');
|
||||||
|
act = require('internal-test-utils').act;
|
||||||
|
useEffect = React.useEffect;
|
||||||
|
});
|
||||||
|
|
||||||
|
// @gate __DEV__ && enableComponentPerformanceTrack
|
||||||
|
it('shows a hint if an update is triggered by a deeply equal object', async () => {
|
||||||
|
const App = function App({items}) {
|
||||||
|
Scheduler.unstable_advanceTime(10);
|
||||||
|
useEffect(() => {}, [items]);
|
||||||
|
};
|
||||||
|
|
||||||
|
Scheduler.unstable_advanceTime(1);
|
||||||
|
const items = ['one', 'two'];
|
||||||
|
await act(() => {
|
||||||
|
ReactNoop.render(<App items={items} />);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(performance.measure.mock.calls).toEqual([
|
||||||
|
[
|
||||||
|
'Mount',
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
devtools: {
|
||||||
|
color: 'warning',
|
||||||
|
properties: null,
|
||||||
|
tooltipText: 'Mount',
|
||||||
|
track: 'Components ⚛',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
end: 11,
|
||||||
|
start: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
performance.measure.mockClear();
|
||||||
|
|
||||||
|
Scheduler.unstable_advanceTime(10);
|
||||||
|
await act(() => {
|
||||||
|
ReactNoop.render(<App items={items.concat('4')} />);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(performance.measure.mock.calls).toEqual([
|
||||||
|
[
|
||||||
|
'App',
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
devtools: {
|
||||||
|
color: 'primary-dark',
|
||||||
|
properties: [
|
||||||
|
['Changed Props', ''],
|
||||||
|
[' items', 'Array'],
|
||||||
|
['+ 2', '…'],
|
||||||
|
],
|
||||||
|
tooltipText: 'App',
|
||||||
|
track: 'Components ⚛',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
end: 31,
|
||||||
|
start: 21,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// @gate __DEV__ && enableComponentPerformanceTrack
|
||||||
|
it('bails out of diffing wide arrays', async () => {
|
||||||
|
const App = function App({items}) {
|
||||||
|
Scheduler.unstable_advanceTime(10);
|
||||||
|
React.useEffect(() => {}, [items]);
|
||||||
|
};
|
||||||
|
|
||||||
|
Scheduler.unstable_advanceTime(1);
|
||||||
|
const items = Array.from({length: 1000}, (_, i) => i);
|
||||||
|
await act(() => {
|
||||||
|
ReactNoop.render(<App items={items} />);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(performance.measure.mock.calls).toEqual([
|
||||||
|
[
|
||||||
|
'Mount',
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
devtools: {
|
||||||
|
color: 'warning',
|
||||||
|
properties: null,
|
||||||
|
tooltipText: 'Mount',
|
||||||
|
track: 'Components ⚛',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
end: 11,
|
||||||
|
start: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
performance.measure.mockClear();
|
||||||
|
|
||||||
|
Scheduler.unstable_advanceTime(10);
|
||||||
|
await act(() => {
|
||||||
|
ReactNoop.render(<App items={items.concat('-1')} />);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(performance.measure.mock.calls).toEqual([
|
||||||
|
[
|
||||||
|
'App',
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
devtools: {
|
||||||
|
color: 'primary-dark',
|
||||||
|
properties: [
|
||||||
|
['Changed Props', ''],
|
||||||
|
[' items', 'Array'],
|
||||||
|
[
|
||||||
|
'Previous object has more than 100 properties. React will not attempt to diff objects with too many properties.',
|
||||||
|
'',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Next object has more than 100 properties. React will not attempt to diff objects with too many properties.',
|
||||||
|
'',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
tooltipText: 'App',
|
||||||
|
track: 'Components ⚛',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
end: 31,
|
||||||
|
start: 21,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// @gate __DEV__ && enableComponentPerformanceTrack
|
||||||
|
it('does not show all properties of wide objects', async () => {
|
||||||
|
const App = function App({items}) {
|
||||||
|
Scheduler.unstable_advanceTime(10);
|
||||||
|
React.useEffect(() => {}, [items]);
|
||||||
|
};
|
||||||
|
|
||||||
|
Scheduler.unstable_advanceTime(1);
|
||||||
|
await act(() => {
|
||||||
|
ReactNoop.render(<App data={{buffer: null}} />);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(performance.measure.mock.calls).toEqual([
|
||||||
|
[
|
||||||
|
'Mount',
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
devtools: {
|
||||||
|
color: 'warning',
|
||||||
|
properties: null,
|
||||||
|
tooltipText: 'Mount',
|
||||||
|
track: 'Components ⚛',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
end: 11,
|
||||||
|
start: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
performance.measure.mockClear();
|
||||||
|
|
||||||
|
Scheduler.unstable_advanceTime(10);
|
||||||
|
|
||||||
|
const bigData = new Uint8Array(1000);
|
||||||
|
await act(() => {
|
||||||
|
ReactNoop.render(<App data={{buffer: bigData}} />);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(performance.measure.mock.calls).toEqual([
|
||||||
|
[
|
||||||
|
'App',
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
devtools: {
|
||||||
|
color: 'primary-dark',
|
||||||
|
properties: [
|
||||||
|
['Changed Props', ''],
|
||||||
|
[' data', ''],
|
||||||
|
['– buffer', 'null'],
|
||||||
|
['+ buffer', 'Uint8Array'],
|
||||||
|
['+ 0', '0'],
|
||||||
|
['+ 1', '0'],
|
||||||
|
['+ 2', '0'],
|
||||||
|
['+ 3', '0'],
|
||||||
|
['+ 4', '0'],
|
||||||
|
['+ 5', '0'],
|
||||||
|
['+ 6', '0'],
|
||||||
|
['+ 7', '0'],
|
||||||
|
['+ 8', '0'],
|
||||||
|
['+ 9', '0'],
|
||||||
|
['+ 10', '0'],
|
||||||
|
['+ 11', '0'],
|
||||||
|
['+ 12', '0'],
|
||||||
|
['+ 13', '0'],
|
||||||
|
['+ 14', '0'],
|
||||||
|
['+ 15', '0'],
|
||||||
|
['+ 16', '0'],
|
||||||
|
['+ 17', '0'],
|
||||||
|
['+ 18', '0'],
|
||||||
|
['+ 19', '0'],
|
||||||
|
['+ 20', '0'],
|
||||||
|
['+ 21', '0'],
|
||||||
|
['+ 22', '0'],
|
||||||
|
['+ 23', '0'],
|
||||||
|
['+ 24', '0'],
|
||||||
|
['+ 25', '0'],
|
||||||
|
['+ 26', '0'],
|
||||||
|
['+ 27', '0'],
|
||||||
|
['+ 28', '0'],
|
||||||
|
['+ 29', '0'],
|
||||||
|
['+ 30', '0'],
|
||||||
|
['+ 31', '0'],
|
||||||
|
['+ 32', '0'],
|
||||||
|
['+ 33', '0'],
|
||||||
|
['+ 34', '0'],
|
||||||
|
['+ 35', '0'],
|
||||||
|
['+ 36', '0'],
|
||||||
|
['+ 37', '0'],
|
||||||
|
['+ 38', '0'],
|
||||||
|
['+ 39', '0'],
|
||||||
|
['+ 40', '0'],
|
||||||
|
['+ 41', '0'],
|
||||||
|
['+ 42', '0'],
|
||||||
|
['+ 43', '0'],
|
||||||
|
['+ 44', '0'],
|
||||||
|
['+ 45', '0'],
|
||||||
|
['+ 46', '0'],
|
||||||
|
['+ 47', '0'],
|
||||||
|
['+ 48', '0'],
|
||||||
|
['+ 49', '0'],
|
||||||
|
['+ 50', '0'],
|
||||||
|
['+ 51', '0'],
|
||||||
|
['+ 52', '0'],
|
||||||
|
['+ 53', '0'],
|
||||||
|
['+ 54', '0'],
|
||||||
|
['+ 55', '0'],
|
||||||
|
['+ 56', '0'],
|
||||||
|
['+ 57', '0'],
|
||||||
|
['+ 58', '0'],
|
||||||
|
['+ 59', '0'],
|
||||||
|
['+ 60', '0'],
|
||||||
|
['+ 61', '0'],
|
||||||
|
['+ 62', '0'],
|
||||||
|
['+ 63', '0'],
|
||||||
|
['+ 64', '0'],
|
||||||
|
['+ 65', '0'],
|
||||||
|
['+ 66', '0'],
|
||||||
|
['+ 67', '0'],
|
||||||
|
['+ 68', '0'],
|
||||||
|
['+ 69', '0'],
|
||||||
|
['+ 70', '0'],
|
||||||
|
['+ 71', '0'],
|
||||||
|
['+ 72', '0'],
|
||||||
|
['+ 73', '0'],
|
||||||
|
['+ 74', '0'],
|
||||||
|
['+ 75', '0'],
|
||||||
|
['+ 76', '0'],
|
||||||
|
['+ 77', '0'],
|
||||||
|
['+ 78', '0'],
|
||||||
|
['+ 79', '0'],
|
||||||
|
['+ 80', '0'],
|
||||||
|
['+ 81', '0'],
|
||||||
|
['+ 82', '0'],
|
||||||
|
['+ 83', '0'],
|
||||||
|
['+ 84', '0'],
|
||||||
|
['+ 85', '0'],
|
||||||
|
['+ 86', '0'],
|
||||||
|
['+ 87', '0'],
|
||||||
|
['+ 88', '0'],
|
||||||
|
['+ 89', '0'],
|
||||||
|
['+ 90', '0'],
|
||||||
|
['+ 91', '0'],
|
||||||
|
['+ 92', '0'],
|
||||||
|
['+ 93', '0'],
|
||||||
|
['+ 94', '0'],
|
||||||
|
['+ 95', '0'],
|
||||||
|
['+ 96', '0'],
|
||||||
|
['+ 97', '0'],
|
||||||
|
['+ 98', '0'],
|
||||||
|
['+ 99', '0'],
|
||||||
|
[
|
||||||
|
'+ Only 100 properties are shown. React will not log more properties of this object.',
|
||||||
|
'',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
tooltipText: 'App',
|
||||||
|
track: 'Components ⚛',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
end: 31,
|
||||||
|
start: 21,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -18,9 +18,13 @@ const EMPTY_ARRAY = 0;
|
||||||
const COMPLEX_ARRAY = 1;
|
const COMPLEX_ARRAY = 1;
|
||||||
const PRIMITIVE_ARRAY = 2; // Primitive values only
|
const PRIMITIVE_ARRAY = 2; // Primitive values only
|
||||||
const ENTRIES_ARRAY = 3; // Tuple arrays of string and value (like Headers, Map, etc)
|
const ENTRIES_ARRAY = 3; // Tuple arrays of string and value (like Headers, Map, etc)
|
||||||
|
|
||||||
|
// Showing wider objects in the devtools is not useful.
|
||||||
|
const OBJECT_WIDTH_LIMIT = 100;
|
||||||
|
|
||||||
function getArrayKind(array: Object): 0 | 1 | 2 | 3 {
|
function getArrayKind(array: Object): 0 | 1 | 2 | 3 {
|
||||||
let kind: 0 | 1 | 2 | 3 = EMPTY_ARRAY;
|
let kind: 0 | 1 | 2 | 3 = EMPTY_ARRAY;
|
||||||
for (let i = 0; i < array.length; i++) {
|
for (let i = 0; i < array.length && i < OBJECT_WIDTH_LIMIT; i++) {
|
||||||
const value = array[i];
|
const value = array[i];
|
||||||
if (typeof value === 'object' && value !== null) {
|
if (typeof value === 'object' && value !== null) {
|
||||||
if (
|
if (
|
||||||
|
|
@ -55,10 +59,23 @@ export function addObjectToProperties(
|
||||||
indent: number,
|
indent: number,
|
||||||
prefix: string,
|
prefix: string,
|
||||||
): void {
|
): void {
|
||||||
|
let addedProperties = 0;
|
||||||
for (const key in object) {
|
for (const key in object) {
|
||||||
if (hasOwnProperty.call(object, key) && key[0] !== '_') {
|
if (hasOwnProperty.call(object, key) && key[0] !== '_') {
|
||||||
|
addedProperties++;
|
||||||
const value = object[key];
|
const value = object[key];
|
||||||
addValueToProperties(key, value, properties, indent, prefix);
|
addValueToProperties(key, value, properties, indent, prefix);
|
||||||
|
if (addedProperties >= OBJECT_WIDTH_LIMIT) {
|
||||||
|
properties.push([
|
||||||
|
prefix +
|
||||||
|
'\xa0\xa0'.repeat(indent) +
|
||||||
|
'Only ' +
|
||||||
|
OBJECT_WIDTH_LIMIT +
|
||||||
|
' properties are shown. React will not log more properties of this object.',
|
||||||
|
'',
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -103,7 +120,9 @@ export function addValueToProperties(
|
||||||
addValueToProperties('key', key, properties, indent + 1, prefix);
|
addValueToProperties('key', key, properties, indent + 1, prefix);
|
||||||
}
|
}
|
||||||
let hasChildren = false;
|
let hasChildren = false;
|
||||||
|
let addedProperties = 0;
|
||||||
for (const propKey in props) {
|
for (const propKey in props) {
|
||||||
|
addedProperties++;
|
||||||
if (propKey === 'children') {
|
if (propKey === 'children') {
|
||||||
if (
|
if (
|
||||||
props.children != null &&
|
props.children != null &&
|
||||||
|
|
@ -123,6 +142,10 @@ export function addValueToProperties(
|
||||||
prefix,
|
prefix,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (addedProperties >= OBJECT_WIDTH_LIMIT) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
properties.push([
|
properties.push([
|
||||||
'',
|
'',
|
||||||
|
|
@ -135,16 +158,21 @@ export function addValueToProperties(
|
||||||
let objectName = objectToString.slice(8, objectToString.length - 1);
|
let objectName = objectToString.slice(8, objectToString.length - 1);
|
||||||
if (objectName === 'Array') {
|
if (objectName === 'Array') {
|
||||||
const array: Array<any> = (value: any);
|
const array: Array<any> = (value: any);
|
||||||
|
const didTruncate = array.length > OBJECT_WIDTH_LIMIT;
|
||||||
const kind = getArrayKind(array);
|
const kind = getArrayKind(array);
|
||||||
if (kind === PRIMITIVE_ARRAY || kind === EMPTY_ARRAY) {
|
if (kind === PRIMITIVE_ARRAY || kind === EMPTY_ARRAY) {
|
||||||
desc = JSON.stringify(array);
|
desc = JSON.stringify(
|
||||||
|
didTruncate
|
||||||
|
? array.slice(0, OBJECT_WIDTH_LIMIT).concat('…')
|
||||||
|
: array,
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
} else if (kind === ENTRIES_ARRAY) {
|
} else if (kind === ENTRIES_ARRAY) {
|
||||||
properties.push([
|
properties.push([
|
||||||
prefix + '\xa0\xa0'.repeat(indent) + propertyName,
|
prefix + '\xa0\xa0'.repeat(indent) + propertyName,
|
||||||
'',
|
'',
|
||||||
]);
|
]);
|
||||||
for (let i = 0; i < array.length; i++) {
|
for (let i = 0; i < array.length && i < OBJECT_WIDTH_LIMIT; i++) {
|
||||||
const entry = array[i];
|
const entry = array[i];
|
||||||
addValueToProperties(
|
addValueToProperties(
|
||||||
entry[0],
|
entry[0],
|
||||||
|
|
@ -154,6 +182,15 @@ export function addValueToProperties(
|
||||||
prefix,
|
prefix,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (didTruncate) {
|
||||||
|
addValueToProperties(
|
||||||
|
OBJECT_WIDTH_LIMIT.toString(),
|
||||||
|
'…',
|
||||||
|
properties,
|
||||||
|
indent + 1,
|
||||||
|
prefix,
|
||||||
|
);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -254,13 +291,39 @@ export function addObjectDiffToProperties(
|
||||||
// If a property is added or removed, we just emit the property name and omit the value it had.
|
// If a property is added or removed, we just emit the property name and omit the value it had.
|
||||||
// Mainly for performance. We need to minimize to only relevant information.
|
// Mainly for performance. We need to minimize to only relevant information.
|
||||||
let isDeeplyEqual = true;
|
let isDeeplyEqual = true;
|
||||||
|
let prevPropertiesChecked = 0;
|
||||||
for (const key in prev) {
|
for (const key in prev) {
|
||||||
|
if (prevPropertiesChecked > OBJECT_WIDTH_LIMIT) {
|
||||||
|
properties.push([
|
||||||
|
'Previous object has more than ' +
|
||||||
|
OBJECT_WIDTH_LIMIT +
|
||||||
|
' properties. React will not attempt to diff objects with too many properties.',
|
||||||
|
'',
|
||||||
|
]);
|
||||||
|
isDeeplyEqual = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (!(key in next)) {
|
if (!(key in next)) {
|
||||||
properties.push([REMOVED + '\xa0\xa0'.repeat(indent) + key, '\u2026']);
|
properties.push([REMOVED + '\xa0\xa0'.repeat(indent) + key, '\u2026']);
|
||||||
isDeeplyEqual = false;
|
isDeeplyEqual = false;
|
||||||
}
|
}
|
||||||
|
prevPropertiesChecked++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let nextPropertiesChecked = 0;
|
||||||
for (const key in next) {
|
for (const key in next) {
|
||||||
|
if (nextPropertiesChecked > OBJECT_WIDTH_LIMIT) {
|
||||||
|
properties.push([
|
||||||
|
'Next object has more than ' +
|
||||||
|
OBJECT_WIDTH_LIMIT +
|
||||||
|
' properties. React will not attempt to diff objects with too many properties.',
|
||||||
|
'',
|
||||||
|
]);
|
||||||
|
isDeeplyEqual = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (key in prev) {
|
if (key in prev) {
|
||||||
const prevValue = prev[key];
|
const prevValue = prev[key];
|
||||||
const nextValue = next[key];
|
const nextValue = next[key];
|
||||||
|
|
@ -368,6 +431,8 @@ export function addObjectDiffToProperties(
|
||||||
properties.push([ADDED + '\xa0\xa0'.repeat(indent) + key, '\u2026']);
|
properties.push([ADDED + '\xa0\xa0'.repeat(indent) + key, '\u2026']);
|
||||||
isDeeplyEqual = false;
|
isDeeplyEqual = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nextPropertiesChecked++;
|
||||||
}
|
}
|
||||||
return isDeeplyEqual;
|
return isDeeplyEqual;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user