react
react copied to clipboard
JSX Outlining
Currently, the react compiler can not compile within callbacks which can potentially cause over rendering. Consider this example:
function Component(countries, onDelete) {
const name = useFoo();
return countries.map(() => {
return (
<Foo>
<Bar name={name}/>
<Baz onclick={onDelete} />
</Foo>
);
});
}
In this case, there's no memoization of the nested jsx elements. But instead if we were to manually refactor the nested jsx into separate component like this:
function Component(countries, onDelete) {
const name = useFoo();
return countries.map(() => {
return <Temp name={name} onDelete={onDelete} />;
});
}
function Temp({ name, onDelete }) {
return (
<Foo>
<Bar name={name} />
<Baz onclick={onDelete} />
</Foo>
);
}
The compiler can now optimise both these components:
function Component(countries, onDelete) {
const $ = _c(4);
const name = useFoo();
let t0;
if ($[0] !== name || $[1] !== onDelete || $[2] !== countries) {
t0 = countries.map(() => <Temp name={name} onDelete={onDelete} />);
$[0] = name;
$[1] = onDelete;
$[2] = countries;
$[3] = t0;
} else {
t0 = $[3];
}
return t0;
}
function Temp(t0) {
const $ = _c(7);
const { name, onDelete } = t0;
let t1;
if ($[0] !== name) {
t1 = <Bar name={name} />;
$[0] = name;
$[1] = t1;
} else {
t1 = $[1];
}
let t2;
if ($[2] !== onDelete) {
t2 = <Baz onclick={onDelete} />;
$[2] = onDelete;
$[3] = t2;
} else {
t2 = $[3];
}
let t3;
if ($[4] !== t1 || $[5] !== t2) {
t3 = (
<Foo>
{t1}
{t2}
</Foo>
);
$[4] = t1;
$[5] = t2;
$[6] = t3;
} else {
t3 = $[6];
}
return t3;
}
Now, when countries
is updated by adding one single value, only the newly added value is re-rendered and not the entire list. Rather than having to do this manually, this PR teaches the react compiler to do this transformation.
This PR adds a new pass (OutlineJsx
) to capture nested jsx statements and outline them in a separate component. This newly outlined component can then by memoized by the compiler, giving us more fine grained rendering.
This PR adds a new HIR node (StartJsx
) to track when the jsx statement starts, which lets us start capturing nested jsx expressions.
There's a lot of improvements we can do in the future:
- For now, we only outline nested jsx inside callbacks, but this can be extended in the future (for ex, to outline nested jsx inside loops).
function Component(arr) {
const jsx = [];
for (const i of arr) {
jsx.push(
// this nested jsx can be outlined
<Bar>
<Baz i={i}></Baz>
</Bar>,
);
}
return jsx;
}
- Only the JSX expression statements are outlined, none of the other statements that flow into the jsx are outlined. This is a bit tricky as we must only outline non-mutating statements using our effects analysis.
function Component(arr) {
return arr.map((i) => {
// this statement should not be outlined
const y = mutate(i);
// this statement can be outlined
const x = { i };
return (
<Bar>
<Baz x={x} y={y}></Baz>
</Bar>
);
});
}