@samitouri / QOS-React / commits / bfd8da807c

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