React 性能跟踪属性
一、作用
二、将对象添加到属性
将对象转化为数组的形式,类似于 Object.entries() 功能。
export function addObjectToProperties(
object: Object,
properties: Array<[string, string]>,
indent: number,
prefix: string,
): void {
let addedProperties = 0;
for (const key in object) {
if (hasOwnProperty.call(object, key) && key[0] !== '_') {
addedProperties++;
const value = object[key];
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;
}
}
}
}
三、将值添加到属性
以特定方式将键(第一参数、第四参数、第五参数构成的新键)与值(第二参数转化后的值)以双元素的数组形式并追加到给定的数组(第三参数)中。
如果值(第二参数)为对象或是数组,还将进行循环递归添加。
export function addValueToProperties(
propertyName: string,
value: mixed,
properties: Array<[string, string]>,
indent: number,
prefix: string,
): void {
let desc;
switch (typeof value) {
case 'object':
if (value === null) {
desc = 'null';
break;
} else {
if (value.$$typeof === REACT_ELEMENT_TYPE) {
// JSX
const typeName = getComponentNameFromType(value.type) || '\u2026';
const key = value.key;
const props: any = value.props;
const propsKeys = Object.keys(props);
const propsLength = propsKeys.length;
if (key == null && propsLength === 0) {
desc = '<' + typeName + ' />';
break;
}
if (
indent < 3 ||
(propsLength === 1 && propsKeys[0] === 'children' && key == null)
) {
desc = '<' + typeName + ' \u2026 />';
break;
}
properties.push([
prefix + '\xa0\xa0'.repeat(indent) + propertyName,
'<' + typeName,
]);
if (key !== null) {
addValueToProperties('key', key, properties, indent + 1, prefix);
}
let hasChildren = false;
let addedProperties = 0;
for (const propKey in props) {
addedProperties++;
if (propKey === 'children') {
if (
props.children != null &&
(!isArray(props.children) || props.children.length > 0)
) {
hasChildren = true;
}
} else if (
hasOwnProperty.call(props, propKey) &&
propKey[0] !== '_'
) {
addValueToProperties(
propKey,
props[propKey],
properties,
indent + 1,
prefix,
);
}
if (addedProperties >= OBJECT_WIDTH_LIMIT) {
break;
}
}
properties.push([
'',
hasChildren ? '>\u2026</' + typeName + '>' : '/>',
]);
return;
}
const objectToString = Object.prototype.toString.call(value);
let objectName = objectToString.slice(8, objectToString.length - 1);
if (objectName === 'Array') {
const array: Array<any> = value as any;
const didTruncate = array.length > OBJECT_WIDTH_LIMIT;
const kind = getArrayKind(array);
if (kind === PRIMITIVE_ARRAY || kind === EMPTY_ARRAY) {
desc = JSON.stringify(
didTruncate
? array.slice(0, OBJECT_WIDTH_LIMIT).concat('…')
: array,
);
break;
} else if (kind === ENTRIES_ARRAY) {
properties.push([
prefix + '\xa0\xa0'.repeat(indent) + propertyName,
'',
]);
for (let i = 0; i < array.length && i < OBJECT_WIDTH_LIMIT; i++) {
const entry = array[i];
addValueToProperties(
entry[0],
entry[1],
properties,
indent + 1,
prefix,
);
}
if (didTruncate) {
addValueToProperties(
OBJECT_WIDTH_LIMIT.toString(),
'…',
properties,
indent + 1,
prefix,
);
}
return;
}
}
if (objectName === 'Promise') {
if (value.status === 'fulfilled') {
// Print the inner value
// 打印内部值
const idx = properties.length;
addValueToProperties(
propertyName,
value.value,
properties,
indent,
prefix,
);
if (properties.length > idx) {
// Wrap the value or type in Promise descriptor.
// 将值或类型包装在 Promise 描述符中。
const insertedEntry = properties[idx];
insertedEntry[1] =
'Promise<' + (insertedEntry[1] || 'Object') + '>';
return;
}
} else if (value.status === 'rejected') {
// Print the inner error
// 打印内部错误
const idx = properties.length;
addValueToProperties(
propertyName,
value.reason,
properties,
indent,
prefix,
);
if (properties.length > idx) {
// Wrap the value or type in Promise descriptor.
// 将值或类型包装在 Promise 描述符中。
const insertedEntry = properties[idx];
insertedEntry[1] = 'Rejected Promise<' + insertedEntry[1] + '>';
return;
}
}
properties.push([
'\xa0\xa0'.repeat(indent) + propertyName,
'Promise',
]);
return;
}
if (objectName === 'Object') {
const proto: any = Object.getPrototypeOf(value);
if (proto && typeof proto.constructor === 'function') {
objectName = proto.constructor.name;
}
}
properties.push([
prefix + '\xa0\xa0'.repeat(indent) + propertyName,
objectName === 'Object' ? (indent < 3 ? '' : '\u2026') : objectName,
]);
if (indent < 3) {
addObjectToProperties(value, properties, indent + 1, prefix);
}
return;
}
case 'function':
if (value.name === '') {
desc = '() => {}';
} else {
desc = value.name + '() {}';
}
break;
case 'string':
if (value === OMITTED_PROP_ERROR) {
desc = '\u2026';
} else {
desc = JSON.stringify(value);
}
break;
case 'undefined':
desc = 'undefined';
break;
case 'boolean':
desc = value ? 'true' : 'false';
break;
default:
desc = String(value);
}
properties.push([prefix + '\xa0\xa0'.repeat(indent) + propertyName, desc]);
}
四、将对象差异添加到属性
信息
返回的是 Boolean 类型的值(判定两个对象是否深层相等),为什么不是 is 为前缀的方法。
export function addObjectDiffToProperties(
prev: Object,
next: Object,
properties: Array<[string, string]>,
indent: number,
): boolean {
// Note: We diff even non-owned properties here but things that are shared end up just the same.
// 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.
//
// 注意:这里我们甚至会对非自有属性进行差异比较,但共享的东西最终结果是一样的。如果某个属性被添加或移除,我们只会
// 输出属性名称而省略它原本的值。主要是为了性能原因。我们需要尽量减少到仅包含相关信息。
let isDeeplyEqual = true;
let prevPropertiesChecked = 0;
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)) {
properties.push([REMOVED + '\xa0\xa0'.repeat(indent) + key, '\u2026']);
isDeeplyEqual = false;
}
prevPropertiesChecked++;
}
let nextPropertiesChecked = 0;
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) {
const prevValue = prev[key];
const nextValue = next[key];
if (prevValue !== nextValue) {
if (indent === 0 && key === 'children') {
// Omit any change inside the top level children prop since it's expected to change
// with any change to children of the component and their props will be logged
// elsewhere but still mark it as a cause of render.
//
// 忽略顶层 children 属性内的任何更改,因为它预期会随着组件的 children 及其属性的更改而更改,
// 这些更改将在其他地方记录,但仍将其标记为渲染的原因。
const line = '\xa0\xa0'.repeat(indent) + key;
properties.push([REMOVED + line, '\u2026'], [ADDED + line, '\u2026']);
isDeeplyEqual = false;
continue;
}
if (indent >= 3) {
// Just fallthrough to print the two values if we're deep.
// This will skip nested properties of the objects.
// 如果我们已经深入,就直接打印这两个值。这将跳过对象的嵌套属性。
} else if (
typeof prevValue === 'object' &&
typeof nextValue === 'object' &&
prevValue !== null &&
nextValue !== null &&
prevValue.$$typeof === nextValue.$$typeof
) {
if (nextValue.$$typeof === REACT_ELEMENT_TYPE) {
if (
prevValue.type === nextValue.type &&
prevValue.key === nextValue.key
) {
// If the only thing that has changed is the props of a nested element, then
// we omit the props because it is likely to be represented as a diff elsewhere.
// 如果唯一改变的是嵌套元素的属性,
// 我们会省略这些属性,因为它们很可能在其他地方以差异的形式表示。
const typeName =
getComponentNameFromType(nextValue.type) || '\u2026';
const line = '\xa0\xa0'.repeat(indent) + key;
const desc = '<' + typeName + ' \u2026 />';
properties.push([REMOVED + line, desc], [ADDED + line, desc]);
isDeeplyEqual = false;
continue;
}
} else {
const prevKind = Object.prototype.toString.call(prevValue);
const nextKind = Object.prototype.toString.call(nextValue);
if (
prevKind === nextKind &&
(nextKind === '[object Object]' || nextKind === '[object Array]')
) {
// Diff nested object
// 比较嵌套对象
const entry = [
UNCHANGED + '\xa0\xa0'.repeat(indent) + key,
nextKind === '[object Array]' ? 'Array' : '',
];
properties.push(entry);
const prevLength = properties.length;
const nestedEqual = addObjectDiffToProperties(
prevValue,
nextValue,
properties,
indent + 1,
);
if (!nestedEqual) {
isDeeplyEqual = false;
} else if (prevLength === properties.length) {
// Nothing notably changed inside the nested object. So this is only a
// change in reference equality. Let's note it.
// 嵌套对象内部没有显著变化。所以这只是引用相等性的变化。我们记录一下。
entry[1] =
'Referentially unequal but deeply equal objects. Consider memoization.';
}
continue;
}
}
} else if (
typeof prevValue === 'function' &&
typeof nextValue === 'function' &&
prevValue.name === nextValue.name &&
prevValue.length === nextValue.length
) {
const prevSrc = Function.prototype.toString.call(prevValue);
const nextSrc = Function.prototype.toString.call(nextValue);
if (prevSrc === nextSrc) {
// This looks like it might be the same function but different closures.
// 这看起来可能是相同的函数,但捕获的上下文不同。
let desc;
if (nextValue.name === '') {
desc = '() => {}';
} else {
desc = nextValue.name + '() {}';
}
properties.push([
UNCHANGED + '\xa0\xa0'.repeat(indent) + key,
desc +
' Referentially unequal function closure. Consider memoization.',
]);
continue;
}
}
// Otherwise, emit the change in property and the values.
// 否则,发出属性及其值的变化。
addValueToProperties(key, prevValue, properties, indent, REMOVED);
addValueToProperties(key, nextValue, properties, indent, ADDED);
isDeeplyEqual = false;
}
} else {
properties.push([ADDED + '\xa0\xa0'.repeat(indent) + key, '\u2026']);
isDeeplyEqual = false;
}
nextPropertiesChecked++;
}
return isDeeplyEqual;
}
五、常量
1. 数组类型
// 空数组
const EMPTY_ARRAY = 0;
// 复数数组
const COMPLEX_ARRAY = 1;
// 原始数组 : 仅限原始值
const PRIMITIVE_ARRAY = 2; // Primitive values only
// 条目数组 : 字符串和数值的元组数组(如 Headers、Map 等)
const ENTRIES_ARRAY = 3; // Tuple arrays of string and value (like Headers, Map, etc)
2. 对象宽度限制
// Showing wider objects in the devtools is not useful.
// 在开发者工具中显示更宽的对象没有用。
const OBJECT_WIDTH_LIMIT = 100;
3.
// 已删除
const REMOVED = '\u2013\xa0';
// 已添加
const ADDED = '+\xa0';
// UNCHANGED
const UNCHANGED = '\u2007\xa0';
六、工具
1. 获取数组类型
function getArrayKind(array: Object): 0 | 1 | 2 | 3 {
let kind: 0 | 1 | 2 | 3 = EMPTY_ARRAY;
for (let i = 0; i < array.length && i < OBJECT_WIDTH_LIMIT; i++) {
const value = array[i];
if (typeof value === 'object' && value !== null) {
if (
isArray(value) &&
value.length === 2 &&
typeof value[0] === 'string'
) {
// Key value tuple
// 键值对
if (kind !== EMPTY_ARRAY && kind !== ENTRIES_ARRAY) {
return COMPLEX_ARRAY;
}
kind = ENTRIES_ARRAY;
} else {
return COMPLEX_ARRAY;
}
} else if (typeof value === 'function') {
return COMPLEX_ARRAY;
} else if (typeof value === 'string' && value.length > 50) {
return COMPLEX_ARRAY;
} else if (kind !== EMPTY_ARRAY && kind !== PRIMITIVE_ARRAY) {
return COMPLEX_ARRAY;
} else {
kind = PRIMITIVE_ARRAY;
}
}
return kind;
}