Что делает 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.

Jan 15, 2025 - 20:33
Что делает React Compiler?

Реакт Компилятор (react compiler) - это инструмент для оптимизации React-приложений. Компилятор включается в работу во время сборки приложения и трансформирует код так, чтобы избежать ненужные ререндеры.

Компилятор анализирует код, составляет его упрощенное представление, находит места, которые можно оптимизировать, трансформирует выбранный код. Реакт Компилятор предполалагет, что код удовлетворяет Правилам Реакта - такой код не сломается от оптимизаций компилятора. Если код не удовлетворяет Правилам, то он будет проигнорирован.

Компилятор применяет к коду, как минимум, две трансформации: мемоизирует результат рендера и результат вычисления выражений внутри компонентов.

Мемоизация результата рендера

Результатом рендера я называю выражение, которое возвращается из компонента: return

{items}
, в этом примере
{items}
- результат рендера. Результат рендера зависит от пропсов и промежуточных значений, вычисленных внутри компонента. Назовем эти значения зависимостями выражения. Когда результат рендера остается постоянным при одних и тех же зависимостях, вычисление выражения можно пропустить. Для этого Реакт Компилятор вставит код, запоминающий последний результат вычисления:
  1. Если выражение вычисляется первый раз, то вычисляет выражение, сохраняет результат в кэш, сохраняет зависимости в кэш.
  2. Если выражение вычисляется повторно, сравнивает новые значения зависимостей с сохраненными в кэше:
  3. Если они равны, использует закэшированное значение выражения.
  4. Если они разные, вычисляет выражение с новыми зависимостями, сохраняет результат вычисления и зависимости в кэш.

Такой компонент:

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>;
}

В этом примере

{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 = <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.