Рендеринг и фиксация
Прежде чем ваши компоненты появятся на экране, React должен их отрендерить. Понимание этапов этого процесса поможет вам проанализировать, как выполняется ваш код, и объяснить его поведение.
Вы узнаете
- Что означает рендеринг в React
- Когда и почему React рендерит компонент
- Этапы отображения компонента на экране
- Почему рендеринг не всегда приводит к обновлению DOM
Представьте, что ваши компоненты — это повара на кухне, которые готовят вкусные блюда из ингредиентов. В этом сценарии React — официант, который принимает заказы от клиентов и приносит им блюда. Этот процесс запроса и обслуживания пользовательского интерфейса состоит из трех этапов:
- Запуск рендеринга (доставка заказа гостя на кухню)
- Рендеринг компонента (приготовление заказа на кухне)
- Запись в DOM (подача заказа на стол)
Trigger
Render
Commit
Шаг 1: Запуск рендеринга
Существует две причины для рендеринга компонента:
- Это первоначальный рендеринг компонента.
- Состояние компонента (или одного из его предков) было обновлено.
Первоначальный рендеринг
При запуске приложения необходимо инициировать первоначальный рендеринг. Фреймворки и песочницы иногда скрывают этот код, но это делается путем вызова createRoot с целевым узлом DOM, а затем вызова его метода render с вашим компонентом:
//Index.js
import Image from './Image.js';
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'))
root.render(<Image />);Попробуйте закомментировать вызов root.render() и посмотрите, как компонент исчезнет!
Перерисовка при обновлении состояния
После первоначального рендеринга компонента вы можете инициировать дальнейшие рендеринги, обновляя его состояние с помощью функции set. Обновление состояния вашего компонента автоматически ставит рендеринг в очередь. (Вы можете представить себе это как гостя ресторана, заказывающего чай, десерт и всевозможные блюда после того, как он сделал свой первый заказ, в зависимости от того, насколько он хочет пить или есть.)
State update...
...triggers...
...render!
Шаг 2: React рендерит ваши компоненты
После того как вы запускаете рендеринг, React вызывает ваши компоненты, чтобы определить, что отобразить на экране. «Рендеринг» — это вызов ваших компонентов React.
- При первоначальном рендеринге React вызовет корневой компонент.
- При последующих рендерингах React вызовет функциональный компонент, обновление состояния которого инициировало рендеринг.
Этот процесс является рекурсивным: если обновленный компонент возвращает какой-либо другой компонент, React в следующий раз отобразит этот компонент, и если этот компонент также что-то возвращает, он отобразит этот компонент, и так далее. Процесс будет продолжаться до тех пор, пока не останется вложенных компонентов и React точно не узнает, что должно отображаться на экране.
В следующем примере React вызовет Gallery() и Image() несколько раз:
//Index.js
import Gallery from './Gallery.js';
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'))
root.render(<Gallery />);- Во время первоначального рендеринга React создаст DOM-узлы для тегов
<section>,<h1>и трех тегов<img>. - Во время повторного рендеринга React вычислит, какие из их свойств, если таковые имеются, изменились с момента предыдущего рендеринга. Он не будет ничего делать с этой информацией до следующего шага — фазы фиксации.
Рендеринг всегда должен быть чистым вычислением:
- Одинаковые входные данные — одинаковый результат. При одинаковых входных данных компонент всегда должен возвращать один и тот же JSX. (Когда кто-то заказывает салат с помидорами, он не должен получать салат с луком!)
- Он занимается только своими делами. Он не должен изменять какие-либо объекты или переменные, существовавшие до рендеринга. (Один заказ не должен влиять на заказы других.)
В противном случае вы можете столкнуться с запутанными ошибками и непредсказуемым поведением по мере того, как ваш код становится все более сложным. При разработке в «строгом режиме» React вызывает функцию каждого компонента дважды, что может помочь выявить ошибки, вызванные нечистыми функциями.
Оптимизация производительности
Поведение по умолчанию, при котором рендерируются все компоненты, вложенные в обновленный компонент, не является оптимальным с точки зрения производительности, если обновленный компонент находится очень высоко в дереве. Если вы столкнулись с проблемой производительности, в разделе Производительность описано несколько способов ее решения. Не оптимизируйте преждевременно!
Шаг 3: React фиксирует изменения в DOM
После рендеринга (вызова) ваших компонентов React изменит DOM.
- ** Для первоначального рендеринга** React будет использовать API DOM
appendChild(), чтобы поместить на экран все созданные им узлы DOM. - Для повторного рендеринга React будет применять минимально необходимые операции (вычисляемые во время рендеринга!), чтобы DOM соответствовал последнему результату рендеринга.
React изменяет узлы DOM только в том случае, если между рендерингами есть различия. Например, вот компонент, который перерисовывается каждую секунду с разными пропсами, передаваемыми от его родителя. Обратите внимание, как вы можете добавить текст в <input>, обновив его value, но текст не исчезает при перерисовке компонента:
//Clock.js
export default function Clock({ time }) {
return (
<>
<h1>{time}</h1>
<input />
</>
);
}//App.js
import { useState, useEffect } from 'react';
import Clock from './Clock.js';
function useTime() {
const [time, setTime] = useState(() => new Date());
useEffect(() => {
const id = setInterval(() => {
setTime(new Date());
}, 1000);
return () => clearInterval(id);
}, []);
return time;
}
export default function App() {
const time = useTime();
return (
<Clock time={time.toLocaleTimeString()} />
);
}Это работает, потому что на этом последнем этапе React обновляет только содержимое <h1> новым значением time. Он видит, что <input> появляется в JSX в том же месте, что и в прошлый раз, поэтому React не затрагивает <input> — или его value!
Эпилог: Рисование браузера
После завершения рендеринга и обновления React DOM браузер перерисует экран. Хотя этот процесс известен как «рендеринг браузера», мы будем называть его «рисованием», чтобы избежать путаницы в документации.
Вывод
- Любое обновление экрана в приложении React происходит в три этапа:
- Триггер
- Рендеринг
- Фиксация
- Вы можете использовать Strict Mode для поиска ошибок в ваших компонентах
- React не затрагивает DOM, если результат рендеринга совпадает с предыдущим
Состояние. Память компонента
Компонентам часто приходится изменять содержимое экрана в результате взаимодействия с пользователем. Ввод текста в форму должен обновлять поле ввода, нажатие кнопки «Далее» в карусели изображений — менять отображаемое изображение, а нажатие кнопки «Купить» — добавлять товар в корзину. Компонентам необходимо «запоминать» вещи - текущее значение ввода, текущее изображение, корзину. В React такая специфическая для компонента память называется *состоянием*.
Состояние как моментальный снимок (Snapshot)
Переменные состояния могут выглядеть как обычные переменные JavaScript, которые можно читать и в которые можно записывать данные. Однако состояние ведет себя скорее как моментальный снимок. Его установка не изменяет уже имеющуюся переменную состояния, а вызывает повторную прорисовку.