mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[compiler] Pass through unmodified props spread when inlining jsx (#30995)
If JSX receives a props spread without additional attributes (besides
`ref` and `key`), we can pass the spread object as a property directly
to avoid the extra object copy.
```
<Test {...propsToSpread} />
// {props: propsToSpread}
<Test {...propsToSpread} a="z" />
// {props: {...propsToSpread, a: "z"}}
```
This commit is contained in:
+45
-18
@@ -23,6 +23,7 @@ import {
|
||||
markPredecessors,
|
||||
reversePostorderBlocks,
|
||||
} from '../HIR/HIRBuilder';
|
||||
import {CompilerError} from '..';
|
||||
|
||||
function createSymbolProperty(
|
||||
fn: HIRFunction,
|
||||
@@ -151,6 +152,16 @@ function createPropsProperties(
|
||||
let refProperty: ObjectProperty | undefined;
|
||||
let keyProperty: ObjectProperty | undefined;
|
||||
const props: Array<ObjectProperty | SpreadPattern> = [];
|
||||
const jsxAttributesWithoutKeyAndRef = propAttributes.filter(
|
||||
p => p.kind === 'JsxAttribute' && p.name !== 'key' && p.name !== 'ref',
|
||||
);
|
||||
const jsxSpreadAttributes = propAttributes.filter(
|
||||
p => p.kind === 'JsxSpreadAttribute',
|
||||
);
|
||||
const spreadPropsOnly =
|
||||
jsxAttributesWithoutKeyAndRef.length === 0 &&
|
||||
jsxSpreadAttributes.length === 1;
|
||||
|
||||
propAttributes.forEach(prop => {
|
||||
switch (prop.kind) {
|
||||
case 'JsxAttribute': {
|
||||
@@ -180,7 +191,6 @@ function createPropsProperties(
|
||||
break;
|
||||
}
|
||||
case 'JsxSpreadAttribute': {
|
||||
// TODO: Optimize spreads to pass object directly if none of its properties are mutated
|
||||
props.push({
|
||||
kind: 'Spread',
|
||||
place: {...prop.argument},
|
||||
@@ -189,6 +199,7 @@ function createPropsProperties(
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const propsPropertyPlace = createTemporaryPlace(fn.env, instr.value.loc);
|
||||
if (children) {
|
||||
let childrenPropProperty: ObjectProperty;
|
||||
@@ -268,23 +279,39 @@ function createPropsProperties(
|
||||
nextInstructions.push(keyInstruction);
|
||||
}
|
||||
|
||||
const propsInstruction: Instruction = {
|
||||
id: makeInstructionId(0),
|
||||
lvalue: {...propsPropertyPlace, effect: Effect.Mutate},
|
||||
value: {
|
||||
kind: 'ObjectExpression',
|
||||
properties: props,
|
||||
loc: instr.value.loc,
|
||||
},
|
||||
loc: instr.loc,
|
||||
};
|
||||
const propsProperty: ObjectProperty = {
|
||||
kind: 'ObjectProperty',
|
||||
key: {name: 'props', kind: 'string'},
|
||||
type: 'property',
|
||||
place: {...propsPropertyPlace, effect: Effect.Capture},
|
||||
};
|
||||
nextInstructions.push(propsInstruction);
|
||||
let propsProperty: ObjectProperty;
|
||||
if (spreadPropsOnly) {
|
||||
const spreadProp = jsxSpreadAttributes[0];
|
||||
CompilerError.invariant(spreadProp.kind === 'JsxSpreadAttribute', {
|
||||
reason: 'Spread prop attribute must be of kind JSXSpreadAttribute',
|
||||
loc: instr.loc,
|
||||
});
|
||||
propsProperty = {
|
||||
kind: 'ObjectProperty',
|
||||
key: {name: 'props', kind: 'string'},
|
||||
type: 'property',
|
||||
place: {...spreadProp.argument, effect: Effect.Mutate},
|
||||
};
|
||||
} else {
|
||||
const propsInstruction: Instruction = {
|
||||
id: makeInstructionId(0),
|
||||
lvalue: {...propsPropertyPlace, effect: Effect.Mutate},
|
||||
value: {
|
||||
kind: 'ObjectExpression',
|
||||
properties: props,
|
||||
loc: instr.value.loc,
|
||||
},
|
||||
loc: instr.loc,
|
||||
};
|
||||
propsProperty = {
|
||||
kind: 'ObjectProperty',
|
||||
key: {name: 'props', kind: 'string'},
|
||||
type: 'property',
|
||||
place: {...propsPropertyPlace, effect: Effect.Capture},
|
||||
};
|
||||
nextInstructions.push(propsInstruction);
|
||||
}
|
||||
|
||||
return {refProperty, keyProperty, propsProperty};
|
||||
}
|
||||
|
||||
|
||||
+62
-35
@@ -28,9 +28,9 @@ function ParentAndRefAndKey(props) {
|
||||
function ParentAndChildren(props) {
|
||||
return (
|
||||
<Parent>
|
||||
<Child key="a" />
|
||||
<Child key="a" {...props} />
|
||||
<Child key="b">
|
||||
<GrandChild className={props.foo} />
|
||||
<GrandChild className={props.foo} {...props} />
|
||||
</Child>
|
||||
</Parent>
|
||||
);
|
||||
@@ -38,7 +38,12 @@ function ParentAndChildren(props) {
|
||||
|
||||
const propsToSpread = {a: 'a', b: 'b', c: 'c'};
|
||||
function PropsSpread() {
|
||||
return <Test {...propsToSpread} />;
|
||||
return (
|
||||
<>
|
||||
<Test {...propsToSpread} />
|
||||
<Test {...propsToSpread} a="z" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
@@ -146,54 +151,59 @@ function ParentAndRefAndKey(props) {
|
||||
}
|
||||
|
||||
function ParentAndChildren(props) {
|
||||
const $ = _c2(3);
|
||||
const $ = _c2(7);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
if ($[0] !== props) {
|
||||
t0 = {
|
||||
$$typeof: Symbol.for("react.transitional.element"),
|
||||
type: Child,
|
||||
ref: null,
|
||||
key: "a",
|
||||
props: {},
|
||||
props: props,
|
||||
};
|
||||
$[0] = t0;
|
||||
$[0] = props;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
t0 = $[1];
|
||||
}
|
||||
let t1;
|
||||
if ($[1] !== props.foo) {
|
||||
if ($[2] !== props) {
|
||||
t1 = {
|
||||
$$typeof: Symbol.for("react.transitional.element"),
|
||||
type: Child,
|
||||
ref: null,
|
||||
key: "b",
|
||||
props: {
|
||||
children: {
|
||||
$$typeof: Symbol.for("react.transitional.element"),
|
||||
type: GrandChild,
|
||||
ref: null,
|
||||
key: null,
|
||||
props: { className: props.foo, ...props },
|
||||
},
|
||||
},
|
||||
};
|
||||
$[2] = props;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
}
|
||||
let t2;
|
||||
if ($[4] !== t0 || $[5] !== t1) {
|
||||
t2 = {
|
||||
$$typeof: Symbol.for("react.transitional.element"),
|
||||
type: Parent,
|
||||
ref: null,
|
||||
key: null,
|
||||
props: {
|
||||
children: [
|
||||
t0,
|
||||
{
|
||||
$$typeof: Symbol.for("react.transitional.element"),
|
||||
type: Child,
|
||||
ref: null,
|
||||
key: "b",
|
||||
props: {
|
||||
children: {
|
||||
$$typeof: Symbol.for("react.transitional.element"),
|
||||
type: GrandChild,
|
||||
ref: null,
|
||||
key: null,
|
||||
props: { className: props.foo },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
props: { children: [t0, t1] },
|
||||
};
|
||||
$[1] = props.foo;
|
||||
$[2] = t1;
|
||||
$[4] = t0;
|
||||
$[5] = t1;
|
||||
$[6] = t2;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
t2 = $[6];
|
||||
}
|
||||
return t1;
|
||||
return t2;
|
||||
}
|
||||
|
||||
const propsToSpread = { a: "a", b: "b", c: "c" };
|
||||
@@ -203,10 +213,27 @@ function PropsSpread() {
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = {
|
||||
$$typeof: Symbol.for("react.transitional.element"),
|
||||
type: Test,
|
||||
type: Symbol.for("react.fragment"),
|
||||
ref: null,
|
||||
key: null,
|
||||
props: { ...propsToSpread },
|
||||
props: {
|
||||
children: [
|
||||
{
|
||||
$$typeof: Symbol.for("react.transitional.element"),
|
||||
type: Test,
|
||||
ref: null,
|
||||
key: null,
|
||||
props: propsToSpread,
|
||||
},
|
||||
{
|
||||
$$typeof: Symbol.for("react.transitional.element"),
|
||||
type: Test,
|
||||
ref: null,
|
||||
key: null,
|
||||
props: { ...propsToSpread, a: "z" },
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
$[0] = t0;
|
||||
} else {
|
||||
|
||||
+8
-3
@@ -24,9 +24,9 @@ function ParentAndRefAndKey(props) {
|
||||
function ParentAndChildren(props) {
|
||||
return (
|
||||
<Parent>
|
||||
<Child key="a" />
|
||||
<Child key="a" {...props} />
|
||||
<Child key="b">
|
||||
<GrandChild className={props.foo} />
|
||||
<GrandChild className={props.foo} {...props} />
|
||||
</Child>
|
||||
</Parent>
|
||||
);
|
||||
@@ -34,7 +34,12 @@ function ParentAndChildren(props) {
|
||||
|
||||
const propsToSpread = {a: 'a', b: 'b', c: 'c'};
|
||||
function PropsSpread() {
|
||||
return <Test {...propsToSpread} />;
|
||||
return (
|
||||
<>
|
||||
<Test {...propsToSpread} />
|
||||
<Test {...propsToSpread} a="z" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
Reference in New Issue
Block a user