Что делает React Compiler?
Реакт Компилятор (react compiler) - это инструмент для оптимизации React-приложений. Компилятор включается в работу во время сборки приложения и трансформирует код так, чтобы избежать ненужные ререндеры. Компилятор анализирует код, составляет его упрощенное представление, находит места, которые можно оптимизировать, трансформирует выбранный код. Реакт Компилятор предполалагет, что код удовлетворяет Правилам Реакта - такой код не сломается от оптимизаций компилятора. Если код не удовлетворяет Правилам, то он будет проигнорирован. Компилятор применяет к коду, как минимум, две трансформации: мемоизирует результат рендера и результат вычисления выражений внутри компонентов. Мемоизация результата рендера Результатом рендера я называю выражение, которое возвращается из компонента: return {items}, в этом примере {items} - результат рендера. Результат рендера зависит от пропсов и промежуточных значений, вычисленных внутри компонента. Назовем эти значения зависимостями выражения. Когда результат рендера остается постоянным при одних и тех же зависимостях, вычисление выражения можно пропустить. Для этого Реакт Компилятор вставит код, запоминающий последний результат вычисления: Если выражение вычисляется первый раз, то вычисляет выражение, сохраняет результат в кэш, сохраняет зависимости в кэш. Если выражение вычисляется повторно, сравнивает новые значения зависимостей с сохраненными в кэше: Если они равны, использует закэшированное значение выражения. Если они разные, вычисляет выражение с новыми зависимостями, сохраняет результат вычисления и зависимости в кэш. Такой компонент: function Div({ item, other }) { return {item}; } Реакт Компилятор преобразует вот так: import { c as _c } from "react/compiler-runtime"; function Div(t0) { const $ = _c(3); const { item, other } = t0; let t1; if ($[0] !== item || $[1] !== other) { t1 = {item}; $[0] = item; $[1] = other; $[2] = t1; } else { t1 = $[2]; } return t1; } Можно догадаться, что в строке const $ = _c(3) Реакт Компилятор создает локальный кэш на 3 элемента. В действительности, c из react/compiler-runtime вызывает внутри себя React.useMemo. В случае, когда компонент использует хуки или контекст, сгенерированный код выглядет немного сложнее, но идея таже: сравнить текущие зависимости компонента с закэшированными, если они поменялись закэшировать новые зависимости, вычислить новое значение, закэшировать новое значение. Код в примере выше аналогичен оборачиванию компонента в memo. Мемоизация выражений В примере выше, jsx-выражение зависило только от пропсов компонента. Часто в компонентах вычисляются промежуточные значения, которые подставляются в jsx-выражения. Эти промежуточные значения также могут быть мемоизированы. Для этого Реакт Компилятор определит зависимости выражения и вставит код для кэширования всего выражения на основе этих зависимостей. Рассмотрим пример, в котором внутри компонента происходит вычисление: export default function App(props) { const name = props.fullname.split(' ')[0]; return {name} ; } В этом примере {name} - это jsx-выражение, его зависимость - переменная name, которая в свою очередь вычисляется выше выражением props.fullname.split(' ')[0]. Выражение props.fullname.split(' ')[0] зависит только от пропсы fullname. Реакт Компилятор преобразует этот компонент следующим образом: import { c as _c } from "react/compiler-runtime"; export default function App(props) { const $ = _c(4); let t0; if ($[0] !== props.fullname) { t0 = props.fullname.split(" "); $[0] = props.fullname; $[1] = t0; } else { t0 = $[1]; } const name = t0[0]; let t1; if ($[2] !== name) { t1 = {name}; $[2] = name; $[3] = t1; } else { t1 = $[3]; } return t1; } Реакт Компилятор закэширует props.fullname и результат вычисления name. Если пропса fullname поменяется, выражение будет вычислено и закэшировано еще раз. Этот код аналогичен оборачиванию вычисления name в useMemo в исходном компоненте: import {useMemo} from 'react'; export default function App(props) { const name = useMemo(() => props.fullname.split(' ')[0], [props.fullname]); return {name} ; } Включение Реакт Компилятора может не принести ощутимой пользы в проекте, в котором осознанно используются useMemo и memo.
Реакт Компилятор (react compiler) - это инструмент для оптимизации React-приложений. Компилятор включается в работу во время сборки приложения и трансформирует код так, чтобы избежать ненужные ререндеры.
Компилятор анализирует код, составляет его упрощенное представление, находит места, которые можно оптимизировать, трансформирует выбранный код. Реакт Компилятор предполалагет, что код удовлетворяет Правилам Реакта - такой код не сломается от оптимизаций компилятора. Если код не удовлетворяет Правилам, то он будет проигнорирован.
Компилятор применяет к коду, как минимум, две трансформации: мемоизирует результат рендера и результат вычисления выражений внутри компонентов.
Мемоизация результата рендера
Результатом рендера я называю выражение, которое возвращается из компонента: return
, в этом примере
- результат рендера. Результат рендера зависит от пропсов и промежуточных значений, вычисленных внутри компонента. Назовем эти значения зависимостями выражения. Когда результат рендера остается постоянным при одних и тех же зависимостях, вычисление выражения можно пропустить. Для этого Реакт Компилятор вставит код, запоминающий последний результат вычисления:
- Если выражение вычисляется первый раз, то вычисляет выражение, сохраняет результат в кэш, сохраняет зависимости в кэш.
- Если выражение вычисляется повторно, сравнивает новые значения зависимостей с сохраненными в кэше:
- Если они равны, использует закэшированное значение выражения.
- Если они разные, вычисляет выражение с новыми зависимостями, сохраняет результат вычисления и зависимости в кэш.
Такой компонент:
function Div({ item, other }) {
return <div data-x={other}>{item}div>;
}
Реакт Компилятор преобразует вот так:
import { c as _c } from "react/compiler-runtime";
function Div(t0) {
const $ = _c(3);
const { item, other } = t0;
let t1;
if ($[0] !== item || $[1] !== other) {
t1 = <div data-x={other}>{item}div>;
$[0] = item;
$[1] = other;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
}
Можно догадаться, что в строке const $ = _c(3)
Реакт Компилятор создает локальный кэш на 3 элемента. В действительности, c
из react/compiler-runtime вызывает внутри себя React.useMemo.
В случае, когда компонент использует хуки или контекст, сгенерированный код выглядет немного сложнее, но идея таже: сравнить текущие зависимости компонента с закэшированными, если они поменялись закэшировать новые зависимости, вычислить новое значение, закэшировать новое значение.
Код в примере выше аналогичен оборачиванию компонента в memo.
Мемоизация выражений
В примере выше, jsx-выражение зависило только от пропсов компонента. Часто в компонентах вычисляются промежуточные значения, которые подставляются в jsx-выражения. Эти промежуточные значения также могут быть мемоизированы. Для этого Реакт Компилятор определит зависимости выражения и вставит код для кэширования всего выражения на основе этих зависимостей.
Рассмотрим пример, в котором внутри компонента происходит вычисление:
export default function App(props) {
const name = props.fullname.split(' ')[0];
return <main>
{name}
main>;
}
В этом примере
- это jsx-выражение, его зависимость - переменная name
, которая в свою очередь вычисляется выше выражением props.fullname.split(' ')[0]
. Выражение props.fullname.split(' ')[0]
зависит только от пропсы fullname
.
Реакт Компилятор преобразует этот компонент следующим образом:
import { c as _c } from "react/compiler-runtime";
export default function App(props) {
const $ = _c(4);
let t0;
if ($[0] !== props.fullname) {
t0 = props.fullname.split(" ");
$[0] = props.fullname;
$[1] = t0;
} else {
t0 = $[1];
}
const name = t0[0];
let t1;
if ($[2] !== name) {
t1 = <main>{name}main>;
$[2] = name;
$[3] = t1;
} else {
t1 = $[3];
}
return t1;
}
Реакт Компилятор закэширует props.fullname
и результат вычисления name
. Если пропса fullname
поменяется, выражение будет вычислено и закэшировано еще раз. Этот код аналогичен оборачиванию вычисления name
в useMemo
в исходном компоненте:
import {useMemo} from 'react';
export default function App(props) {
const name = useMemo(() => props.fullname.split(' ')[0], [props.fullname]);
return <main>
{name}
main>;
}
Включение Реакт Компилятора может не принести ощутимой пользы в проекте, в котором осознанно используются useMemo и memo.