Рендеринг — это самая важная процедура, которой должен управлять программист во фронтенд-разработке. В React метод render() является единственным обязательным методом в компоненте класса, и он отвечает за описание представления, которое должно отображаться в окне браузера. В сочетании с умным способом, которым React работает с концепцией виртуального DOM, существуют определенные тонкости в том, как работает этот метод, и понимание их принесет большую пользу любому начинающему разработчику React.

На протяжении всего письма я буду ссылаться на эту кодовую ручку для демонстрации обсуждаемого поведения.

1. render() 101

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

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

В жизненном цикле это сценарии, в которых вызывается рендеринг:

  • После первого создания экземпляра компонента React после вызова constructor().
  • После обновления реквизитов компонента
  • После setState() звонка

Если в этот момент у вас открыт Codepen, прежде чем что-либо будет отрендерено, вы увидите 2 предупреждающих сообщения от браузера: "render() is called in Parent component!" и "render() is called in Child component!". Эти сообщения вызываются из соответствующих методов родительского и дочернего компонентов примера. Они служат для введения первого случая вызова: когда компонент создается впервые.

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

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

Наглядную и интерактивную ссылку на жизненный цикл React можно найти здесь.

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

shouldComponentUpdate() предоставляет вам nextProp и nextState в качестве аргументов, что позволяет сравнивать текущее состояние и реквизиты компонента. Например, этот блок кода будет вызываться только при изменении реквизита:

Упомянутые выше характеристики и поведение сделали необходимым, чтобы это была чистая функция. Это означает, что внутри вы не должны обновлять состояния или реквизиты компонента (никаких вызовов setState() и обновлений состояния Redux). Это имеет смысл, потому что обновление компонента вызовет новый вызов, который потенциально может заблокировать вас в бесконечном цикле рендеринга.

Что касается возвращаемого значения: render() возвращает один элемент JSX, как упоминалось выше. Это связано с определенными последствиями:

2. Notes on reconciliation, and the key prop

Затем метод render() в каждом компоненте React используется в так называемом алгоритме согласования. Это основной алгоритм, определяющий, как React отображает реальный DOM в вашем браузере на основе виртуального DOM, поддерживаемого внутри React. Чтобы разумно определить, что нужно отображать при каждом вызове, React сравнивает текущее состояние виртуального DOM с реальным и вносит изменения в физическую DOM только в том случае, если он распознает, что пользовательский интерфейс был обновлен.

Хотя не все детали алгоритма согласования React известны, характеристик, подробно описанных в официальной документации, достаточно, чтобы мы начали поэтапно отказываться от некоторых неоптимальных паттернов рендеринга, таким образом, написав более надежный render() метод.

  • Об обновлении пользовательского интерфейса: поскольку дерево DOM анализируется сверху вниз, если обнаруживается несоответствие элементов, React разорвет и перестроит все поддерево, начиная с этого элемента. Если поддерево сложное, эта операция может быть весьма дорогостоящей. Следовательно, если новый элемент должен быть введен в дерево DOM, он должен быть добавлен как последний элемент на этом уровне, если нет особых требований к тому, где он должен быть размещен.

Например, учитывая это поддерево DOM:

<div>
  <span key="li-1">list item 1</span>
  <span key="li-2">list item 2</span>
  <span key="li-3">list item 3</span>
</div>

Если a затем добавляется в начало списка:

<div>
  <!-- previously <span>list item 1</span> - element is detached and <NewComponent /> instantiated -->
  <NewComponent />
  <!-- previously <span>list item 2</span> - content will be updated to "list item 1"  -->
  <span>list item 1</span>
  <!-- previously <span>list item 3</span> - content will be updated to "list item 2"  -->
  <span>list item 2</span>
  <!-- new <span>list item 3</span> is element created  -->
  <span>list item 3</span>
</div>

Если вместо этого добавляется внизу:

<div>
  <!-- previously <span>list item 1</span> - no change -->
  <span>list item 1</span>
  <!-- previously <span>list item 2</span> - no change -->
  <span>list item 2</span>
  <!-- previously <span>list item 3</span> - no change -->
  <span>list item 3</span>
  <!-- new instance of <NewComponent /> is added -->
  <NewComponent />
</div>

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

  • Если вы когда-либо пытались использовать метод map() для перебора массива для отображения списка элементов, вполне вероятно, что вы видели, как React жаловался на отсутствие реквизита key для каждого отображаемого элемента списка. Так что же на самом деле делает key?
  • key — это способ React распознавать элементы в дереве DOM, наступает время согласования. Когда React анализирует все дочерние элементы элемента, он может использовать ключи для сопоставления элементов, которые присутствовали в последнем обновлении. Это позволяет менять порядок дочерних элементов, не вмешиваясь в алгоритм. Пока ключ совпадает между обновлениями, React сохранит конфигурацию элемента.

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

<div>
  <span key="li-1">list item 1</span>
  <span key="li-2">list item 2</span>
  <span key="li-3">list item 3</span>
</div>

В этом случае, если бы мы добавили дополнительный «элемент списка 4» вверху списка, это не привело бы к такому же снижению производительности:

<div>
  <!-- new item - <span> is instantiated -->
  <span key="li-4">list item 4</span>
  <!-- matched with the old "li-1" key - element stays unchanged between renders -->
  <span key="li-1">list item 1</span>
  <!-- matched with the old "li-2" key - element stays unchanged between renders -->
  <span key="li-2">list item 2</span>
  <!-- matched with the old "li-3" key - element stays unchanged between renders -->
  <span key="li-3">list item 3</span>
</div>

В случае, если поддерево создается с использованием map() или других итерационных методов, React требует, чтобы ключи были предоставлены вместе с элементом. Однако даже в случае добавления поддерева DOM вручную ключи должны быть предоставлены для поддеревьев со сложным поведением в отношении условного рендеринга.

3. Parting words

React — это умная среда, которая обеспечивает производительность благодаря своей схеме рендеринга, поэтому, как разработчики, мы должны использовать ее должным образом, чтобы помочь создавать производительные приложения. С учетом сказанного, это не чудо-устройство, которое оптимизирует неэффективность со стороны разработчика, а инструмент, который нужно использовать. Понимание метода render(), а также его значения для алгоритма согласования — это самый первый шаг к тому, чтобы убедиться, что мы используем фреймворк, а не работаем против него, а также один из первых шагов к освоению React.

Первоначально опубликовано на https://www.loginradius.com.