- Published on
Что делает React Compiler?
- Authors
- Name
- Jenny Pollard
Реакт Компилятор (react compiler) - это инструмент для оптимизации React-приложений. Компилятор включается в работу во время сборки приложения и трансформирует код так, чтобы избежать ненужные ререндеры.
Компилятор анализирует код, составляет его упрощенное представление, находит места, которые можно оптимизировать, трансформирует выбранный код. Реакт Компилятор предполалагет, что код удовлетворяет Правилам Реакта - такой код не сломается от оптимизаций компилятора. Если код не удовлетворяет Правилам, то он будет проигнорирован.
Компилятор применяет к коду, как минимум, две трансформации: мемоизирует результат рендера и результат вычисления выражений внутри компонентов.
Мемоизация результата рендера
Результатом рендера я называю выражение, которое возвращается из компонента: return <div>{items}</div>
, в этом примере <div>{items}</div>
- результат рендера. Результат рендера зависит от пропсов и промежуточных значений, вычисленных внутри компонента. Назовем эти значения зависимостями выражения. Когда результат рендера остается постоянным при одних и тех же зависимостях, вычисление выражения можно пропустить. Для этого Реакт Компилятор вставит код, запоминающий последний результат вычисления:
- Если выражение вычисляется первый раз, то вычисляет выражение, сохраняет результат в кэш, сохраняет зависимости в кэш.
- Если выражение вычисляется повторно, сравнивает новые значения зависимостей с сохраненными в кэше:
- Если они равны, использует закэшированное значение выражения.
- Если они разные, вычисляет выражение с новыми зависимостями, сохраняет результат вычисления и зависимости в кэш.
Такой компонент:
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>
}
В этом примере <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.