Перейти к содержанию

Введение

React Compiler — новый инструмент времени сборки, который автоматически оптимизирует приложение React. Он работает с обычным JavaScript и понимает Правила React, поэтому переписывать код, чтобы им пользоваться, не нужно.

Вы узнаете

  • Что делает React Compiler
  • Как начать работу с компилятором
  • Стратегии постепенного внедрения
  • Отладка и устранение неполадок, когда что-то идёт не так
  • Использование компилятора в библиотеке React

Что делает React Compiler?

React Compiler автоматически оптимизирует приложение React во время сборки. React часто и без оптимизации достаточно быстр, но иногда приходится вручную мемоизировать компоненты и значения, чтобы приложение оставалось отзывчивым. Такая ручная мемоизация утомительна, в ней легко ошибиться, и она добавляет лишний код, который нужно сопровождать. React Compiler делает эту оптимизацию автоматически, снимая эту умственную нагрузку, чтобы вы могли сосредоточиться на создании возможностей.

До React Compiler

Без компилятора, чтобы оптимизировать повторные рендеринги, компоненты и значения нужно мемоизировать вручную:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import { useMemo, useCallback, memo } from 'react';

const ExpensiveComponent = memo(function ExpensiveComponent({ data, onClick }) {
  const processedData = useMemo(() => {
    return expensiveProcessing(data);
  }, [data]);

  const handleClick = useCallback((item) => {
    onClick(item.id);
  }, [onClick]);

  return (
    <div>
      {processedData.map(item => (
        <Item key={item.id} onClick={() => handleClick(item)} />
      ))}
    </div>
  );
});

Примечание

В этой ручной мемоизации есть тонкая ошибка, которая ломает мемоизацию:

1
<Item key={item.id} onClick={() => handleClick(item)} />

Хотя handleClick обёрнут в useCallback, стрелочная функция () => handleClick(item) создаёт новую функцию при каждом рендеринге компонента. Это значит, что Item всегда получает новый проп onClick, и мемоизация ломается.

React Compiler умеет оптимизировать это правильно и со стрелочной функцией, и без неё, гарантируя, что Item повторно рендерится, только когда меняется props.onClick.

После React Compiler

С React Compiler вы пишете тот же код без ручной мемоизации:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
function ExpensiveComponent({ data, onClick }) {
  const processedData = expensiveProcessing(data);

  const handleClick = (item) => {
    onClick(item.id);
  };

  return (
    <div>
      {processedData.map(item => (
        <Item key={item.id} onClick={() => handleClick(item)} />
      ))}
    </div>
  );
}

Посмотрите этот пример в песочнице React Compiler

React Compiler автоматически применяет оптимальную мемоизацию и гарантирует, что приложение повторно рендерится, только когда это необходимо.

Какую мемоизацию добавляет React Compiler?

Автоматическая мемоизация React Compiler в первую очередь направлена на улучшение производительности обновлений (повторный рендеринг уже существующих компонентов), поэтому она сосредоточена на двух сценариях:

  1. Пропуск каскадного повторного рендеринга компонентов
    • Повторный рендеринг <Parent /> заставляет повторно рендериться многие компоненты в его дереве, хотя изменился только <Parent />
  2. Пропуск дорогих вычислений вне React
    • Например, вызов expensivelyProcessAReallyLargeArrayOfObjects() внутри компонента или хука, которому нужны эти данные

Оптимизация повторных рендерингов

React позволяет описывать интерфейс как функцию текущего состояния (конкретнее: пропсов, состояния и контекста). В текущей реализации, когда состояние компонента меняется, React повторно рендерит этот компонент и всех его потомков — если только вы не применили какую-то ручную мемоизацию с помощью useMemo(), useCallback() или React.memo(). Например, в следующем примере <MessageButton> будет повторно рендериться всякий раз, когда меняется состояние <FriendList>:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
function FriendList({ friends }) {
  const onlineCount = useFriendOnlineCount();
  if (friends.length === 0) {
    return <NoFriends />;
  }
  return (
    <div>
      <span>{onlineCount} online</span>
      {friends.map((friend) => (
        <FriendListCard key={friend.id} friend={friend} />
      ))}
      <MessageButton />
    </div>
  );
}
Посмотрите этот пример в песочнице React Compiler

React Compiler автоматически применяет эквивалент ручной мемоизации и гарантирует, что при изменении состояния повторно рендерятся только нужные части приложения. Иногда это называют «мелкозернистой реактивностью». В примере выше React Compiler определяет, что возвращаемое значение <FriendListCard /> можно переиспользовать, даже когда friends меняется, и может избежать пересоздания этого JSX и повторного рендеринга <MessageButton>, когда счётчик меняется.

Дорогие вычисления тоже мемоизируются

React Compiler также может автоматически мемоизировать дорогие вычисления, которые используются во время рендеринга:

1
2
3
4
5
6
7
8
9
// **Not** memoized by React Compiler, since this is not a component or hook
function expensivelyProcessAReallyLargeArrayOfObjects() { /* ... */ }

// Memoized by React Compiler since this is a component
function TableContainer({ items }) {
  // This function call would be memoized:
  const data = expensivelyProcessAReallyLargeArrayOfObjects(items);
  // ...
}
Посмотрите этот пример в песочнице React Compiler

Однако если expensivelyProcessAReallyLargeArrayOfObjects действительно дорогая функция, возможно, стоит реализовать для неё собственную мемоизацию вне React, потому что:

  • React Compiler мемоизирует только компоненты и хуки React, а не каждую функцию
  • Мемоизация React Compiler не разделяется между несколькими компонентами или хуками

Поэтому если expensivelyProcessAReallyLargeArrayOfObjects используется во многих разных компонентах, дорогое вычисление будет выполняться снова и снова, даже если передаются в точности те же элементы. Мы рекомендуем сначала профилировать и убедиться, что вычисление действительно настолько дорогое, прежде чем усложнять код.

Стоит ли попробовать компилятор?

Мы призываем всех начать использовать React Compiler. Сегодня компилятор всё ещё необязательное дополнение к React, но в будущем некоторым возможностям он может понадобиться, чтобы работать в полную силу.

Безопасно ли его использовать?

React Compiler теперь стабилен и тщательно проверен в производственной среде. Им уже пользуются в производственной среде такие компании, как Meta, но выкатка компилятора в производственную среду вашего приложения будет зависеть от состояния кодовой базы и от того, насколько хорошо вы следуете Правилам React.

Какие инструменты сборки поддерживаются?

React Compiler можно установить в нескольких инструментах сборки, таких как Babel, Vite, Metro и Rsbuild.

React Compiler — это в первую очередь лёгкая обёртка в виде плагина Babel вокруг ядра компилятора, которое спроектировано так, чтобы не зависеть от самого Babel. Начальная стабильная версия компилятора останется в первую очередь плагином Babel, но мы работаем с командами swc и oxc, чтобы сделать первоклассную поддержку React Compiler, и в будущем вам не придётся возвращать Babel в конвейеры сборки.

Пользователи Next.js могут включить React Compiler, вызываемый через swc, начиная с v15.3.1.

Что делать с useMemo, useCallback и React.memo?

По умолчанию React Compiler мемоизирует код на основе своего анализа и эвристик. В большинстве случаев эта мемоизация будет такой же точной или даже точнее той, что вы написали бы сами.

Однако иногда разработчикам нужен больший контроль над мемоизацией. Хуки useMemo и useCallback можно по-прежнему использовать вместе с React Compiler как аварийный люк, чтобы управлять тем, какие значения мемоизируются. Типичный случай — когда мемоизированное значение используется как зависимость эффекта, чтобы эффект не срабатывал повторно, даже если его зависимости по сути не изменились.

Для нового кода мы рекомендуем полагаться на компилятор в мемоизации и использовать useMemo/useCallback там, где нужен точный контроль.

Для существующего кода мы рекомендуем либо оставить текущую мемоизацию на месте (её удаление может изменить результат компиляции), либо тщательно протестировать код, прежде чем убирать мемоизацию.

Попробуйте React Compiler

Этот раздел поможет начать работу с React Compiler и понять, как эффективно использовать его в проектах.

Дополнительные материалы

Помимо этих документов мы рекомендуем заглянуть в рабочую группу React Compiler, где есть дополнительная информация и обсуждение компилятора.

Источник — https://react.dev/learn/react-compiler/introduction

Комментарии