Рендер и фиксация

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

You will learn

  • Что означает рендеринг в React.
  • Когда и почему React рендерит компонент.
  • Этапы отображения компонента на экране.
  • Почему рендеринг не всегда приводит к обновлению DOM.

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

  1. Триггер рендеринга (передача заказа гостя на кухню)
  2. Рендеринг компонента (приготовление заказа на кухне)
  3. Фиксация в DOM (подача блюда на стол)
  1. React as a server in a restaurant, fetching orders from the users and delivering them to the Component Kitchen.
    Trigger
  2. The Card Chef gives React a fresh Card component.
    Render
  3. React delivers the Card to the user at their table.
    Commit

Illustrated by Rachel Lee Nabors

Step 1: Триггер рендера

Рендер компонента происходит по двум причинам:

  1. Это его первоначальный рендеринг.
  2. Его стейт (или стейт его родителя) был обновлён.

Начальный рендер

Когда ваше приложение запускается, вам необходимо вызвать начальный рендеринг. Фреймворки и песочницы иногда скрывают этот код, но он выполняется вызовом функции createRoot с целевым узлом DOM, а затем вызывая его метод render вашим компонентом:

import Image from './Image.js';
import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root'))
root.render(<Image />);

Попробуйте закомментировать вызов root.render() и увидите, как компонент исчезнет!

Ре-рендер, когда стейт обновляется

После того как компонент был первоначально отрендерен, вы можете инициировать последующие рендеры, обновляя его состояние с помощью функции set Обновление стейта компонента автоматически ставит его в очередь на рендер. (Вы можете представить это как посетителя ресторана, который после первого заказа заказывает чай, десерт и всевозможные вещи, в зависимости от состояния жажды или голода).

  1. React as a server in a restaurant, serving a Card UI to the user, represented as a patron with a cursor for their head. They patron expresses they want a pink card, not a black one!
    State update...
  2. React returns to the Component Kitchen and tells the Card Chef they need a pink Card.
    ...triggers...
  3. The Card Chef gives React the pink Card.
    ...render!

Illustrated by Rachel Lee Nabors

Step 2: React рендерит ваш компонент

После запуска рендера React вызывает ваши компоненты, чтобы определить, что отобразить на экране. *“Рендеринг” — это обращение React к вашим компонентам.

  • На начальном рендере, React вызовет корневой компонент.
  • Для последующих рендерингов React будет вызывать функцию компонента, обновление стейта которого вызвало рендеринг.

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

В следующем примере React вызовет Gallery() и Image() несколько раз:

export default function Gallery() {
  return (
    <section>
      <h1>Inspiring Sculptures</h1>
      <Image />
      <Image />
      <Image />
    </section>
  );
}

function Image() {
  return (
    <img
      src="https://i.imgur.com/ZF6s192.jpg"
      alt="'Floralis Genérica' by Eduardo Catalano: a gigantic metallic flower sculpture with reflective petals"
    />
  );
}

  • На начальном рендере, React создаст DOM ноды для <section>, <h1>, и трёзх <img> тегов.
  • Во время повторного ре-рендеринга, React вычислит, какие из их свойств, если таковые имеются, изменились с момента предыдущего рендеринга. Он ничего не будет делать с этой информацией до следующего шага, фазы фиксации.

Pitfall

Рендеринг должен всегда быть pure calculation:

  • Одинаковые входные данные, одинаковые выходящие. При одинаковых входящих данных компонент всегда должен возвращать один и тот же JSX. (Когда кто-то заказывает салат с помидорами, то он не должен получить салат с луком!)
  • Своими делами заниматься. Не изменять объекты или переменные, существовавшие до рендеринга. (Один заказ не должен изменять чей-либо другой заказ).

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

Deep Dive

Оптимизируем производительность

Поведение по умолчанию — рендеринг всех компонентов, вложенных в обновленный компонент, — не является оптимальным с точки зрения производительности, если обновляемый компонент находится очень высоко в дереве. Если вы столкнулись с проблемой производительности, есть несколько способов ее решения, описанных в разделе Производительность. Не оптимизируйте преждевременно!

Step 3: React фиксирует изменения в DOM

После рендеринга (вызова) ваших компонентов React модифицирует DOM.

  • На начальном рендере, React использует appendChild() DOM API, чтобы вставить все DOM ноды, которые он создал на экране.
  • Для ре-рендеров, React будет применять минимально необходимые операции (вычисляемые во время рендеринга!), чтобы DOM соответствовал последнему выводу рендеринга.

** React изменяет узлы DOM, только если есть разница между рендерами.** Например, вот компонент, который рендерится с разными пропсами, передаваемыми от родителя каждую секунду. Обратите внимание, как вы можете добавить некоторый текст в <input>, обновляя его значение, но текст не исчезает при повторном рендеринге компонента:

export default function Clock({ time }) {
  return (
    <>
      <h1>{time}</h1>
      <input />
    </>
  );
}

Это работает, потому что в предыдущий раз, React обновил значение <h1> с новым time. Он видит, что <input> появляется в том же месте JSX, поэтому React не трогает <input>— или его value!

Epilogue: Browser paint

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

A browser painting 'still life with card element'.

Illustrated by Rachel Lee Nabors

Recap

  • Любое обновление экрана в приложении React происходит в три этапа:
    1. Триггер
    2. Рендеринг
    3. Фиксация
  • Вы можете использовать режим Strict Mode для поиска ошибок в ваших компонентах.
  • React не трогает DOM, если результат рендеринга такой же, как и в прошлый раз.