Make class prop resolution faster (#28766)
`delete` causes an object (in V8, and maybe other engines) to deopt to a dictionary instead of a class. Instead of `assign` + `delete`, manually iterate over all the properties, like the JSX runtime does. To avoid copying the object twice I moved the `ref` prop removal to come before handling default props. If we already cloned the props to remove `ref`, then we can skip cloning again to handle default props.
Andrew Clark committed
Apr 5, 2024 at 13:06 UTC
bfd8da807c75a2d123627415f9eaf2d36ac3ed6a
2 files changed
+44
-25
packages/react-reconciler/src/ReactFiberClassComponent.js
+19
-12
@@ -1251,7 +1251,19 @@ export function resolveClassComponentProps(
1251
): Object {
1252
let newProps = baseProps;
1253
1254
- // Resolve default props. Taken from old JSX runtime, where this used to live.
1254
+ if (enableRefAsProp) {
1255
+ // Remove ref from the props object, if it exists.
1256
+ if ('ref' in baseProps) {
1257
+ newProps = ({}: any);
1258
+ for (const propName in baseProps) {
1259
+ if (propName !== 'ref') {
1260
+ newProps[propName] = baseProps[propName];
1261
+ }
1262
+ }
1263
+ }
1264
+ }
1265
+
1266
+ // Resolve default props.
1267
const defaultProps = Component.defaultProps;
1268
if (
1269
defaultProps &&
@@ -1259,7 +1271,12 @@ export function resolveClassComponentProps(
1271
// default props here in the reconciler, rather than in the JSX runtime.
1272
(disableDefaultPropsExceptForClasses || !alreadyResolvedDefaultProps)
1273
) {
1262
- newProps = assign({}, newProps, baseProps);
1274
+ // We may have already copied the props object above to remove ref. If so,
1275
+ // we can modify that. Otherwise, copy the props object with Object.assign.
1276
+ if (newProps === baseProps) {
1277
+ newProps = assign({}, newProps, baseProps);
1278
+ }
1279
+ // Taken from old JSX runtime, where this used to live.
1280
for (const propName in defaultProps) {
1281
if (newProps[propName] === undefined) {
1282
newProps[propName] = defaultProps[propName];
@@ -1267,16 +1284,6 @@ export function resolveClassComponentProps(
1284
}
1285
}
1286
1270
- if (enableRefAsProp) {
1271
- // Remove ref from the props object, if it exists.
1272
- if ('ref' in newProps) {
1273
- if (newProps === baseProps) {
1274
- newProps = assign({}, newProps);
1275
- }
1276
- delete newProps.ref;
1277
- }
1278
- }
1279
-
1287
return newProps;
1288
}
1289
packages/react-server/src/ReactFizzServer.js
+25
-13
@@ -1397,24 +1397,36 @@ export function resolveClassComponentProps(
1397
): Object {
1398
let newProps = baseProps;
1399
1400
- // Resolve default props. Taken from old JSX runtime, where this used to live.
1401
- const defaultProps = Component.defaultProps;
1402
- if (defaultProps && disableDefaultPropsExceptForClasses) {
1403
- newProps = assign({}, newProps, baseProps);
1404
- for (const propName in defaultProps) {
1405
- if (newProps[propName] === undefined) {
1406
- newProps[propName] = defaultProps[propName];
1400
+ if (enableRefAsProp) {
1401
+ // Remove ref from the props object, if it exists.
1402
+ if ('ref' in baseProps) {
1403
+ newProps = ({}: any);
1404
+ for (const propName in baseProps) {
1405
+ if (propName !== 'ref') {
1406
+ newProps[propName] = baseProps[propName];
1407
+ }
1408
}
1409
}
1410
}
1411
1411
- if (enableRefAsProp) {
1412
- // Remove ref from the props object, if it exists.
1413
- if ('ref' in newProps) {
1414
- if (newProps === baseProps) {
1415
- newProps = assign({}, newProps);
1412
+ // Resolve default props.
1413
+ const defaultProps = Component.defaultProps;
1414
+ if (
1415
+ defaultProps &&
1416
+ // If disableDefaultPropsExceptForClasses is true, we always resolve
1417
+ // default props here, rather than in the JSX runtime.
1418
+ disableDefaultPropsExceptForClasses
1419
+ ) {
1420
+ // We may have already copied the props object above to remove ref. If so,
1421
+ // we can modify that. Otherwise, copy the props object with Object.assign.
1422
+ if (newProps === baseProps) {
1423
+ newProps = assign({}, newProps, baseProps);
1424
+ }
1425
+ // Taken from old JSX runtime, where this used to live.
1426
+ for (const propName in defaultProps) {
1427
+ if (newProps[propName] === undefined) {
1428
+ newProps[propName] = defaultProps[propName];
1429
}
1417
- delete newProps.ref;
1430
}
1431
}
1432