Алгоритм оптимизации React

Давно пылятся по тёмным чуланам прежние рабочие лошадки - «Pentium IV» и «Athlon XP 2500+» с разбитыми клавиатурами от написания ночных лабораторок на Delphi. И многим уже кажется, 32 гигабайта мозгов произведенных на Тайване вывозит нам любой продукт на скорости в пару махов выше крылатой ракеты.

Однако каждый прекрасный проект в своём фундаменте имеет прочную основу из мощнейших алгоритмов оптимизации. В React одним из этих краеугольных камней является алгоритм «согласования» или reconciliation

Как работает алгоритм «согласования»?

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

Вот упрощенный вариант его работы:

  1. В начале при запуске приложения React из имеющихся нод-узлов собирает первое дерево, которое принято называть Current tree, и в среде Rendering Enviorement на основе приоритетов отрисовывает первое DOM дерево, что мы в последствии увидим как стартовую web-страницу.
  2. Далее, на стадии, когда в ответ на действие или событие на нашем сайте происходят изменения, строится новое дерево Work-in-progress tree, которое, в свою очередь, тут же сравнивается с Current tree для определения различий. Вот именно только эти различия и передаются затем в среду Rendering Enviorement для повторной отрисовки и обновления DOM. Теперь Work-in-progress tree превращается в Current tree до следующего изменения, где данный цикл повторится.

Выход был найден

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

Этот алгоритм и получил название «diffing» или «согласование» и включает в себя следующие предположения. Чтобы обновления компонентов были предсказуемыми и при этом достаточно быстрыми.

Во-первых: Два элемента разных типов будут производить разные деревья. Замена корневого типа div на тип nav в компоненте приведёт к размонтированию старого дерева со всеми дочерними элементами и потере их состояния, а затем дерево будет отрисовано заново. Выполняя сравнивание двух элементов React DOM одного типа React смотрит на атрибуты обоих, при необходимости обновляет измененные атрибуты, например, className.

Во-вторых: программист может указать, какие элементы могут не подвергаться повторному рендеренгу, если укажет им уникальное своство key.

Данный процесс согласования, когда идеальное «виртуальное» представление UI содержится в памяти и сопоставляется с «реальным» DOM библиотекой ReactDOM, именуют концепцией Виртуального DOM (Virtual DOM). Виртуальный DOM осуществляет декларативный подход, когда разработчик указывает, в каком виде должен находиться UI. React делает, чтобы DOM находился в этом состоянии. Тем самым мы абстрагируем обработку событий, действия с атрибутами, обновление DOM.

В контексте React «виртуальный DOM» обычно ассоциируется с React-элементами поскольку они являются объектами, представляющими UI. Однако, React имеет и внутренние объекты - fibers, они хранят дополнительную информацию о дереве компонентов. Их можно отнести к реализации «виртуального DOM» в React.

Тип key имеет значение

Представим, что у нас есть список из пяти элементов, а key у нас принимает index элемента, и мы хотим что-то удалить из списка.

Если мы удалим его в конце списка, то при сопоставлении деревьев мутация проходит быстро, ведь четыре элемента будут последовательно эквивалентны друг-другу. Но, попытавшись удалить элемент в начале списка, мы поставим React в положение, где он будет вынужден вызывать методы жизненного цикла у всех последующих элементов, ведь теперь на месте удалённого элемента Current tree стоит новый элемент из Work-in-progress tree. И чем больше у нас элементов, тем больше методов жизненного цикла будет вызвано. Именно по таким причинам и не рекомендовано использовать index в качестве уникального свойства key. Вы же помните, что происходит с массивом при удалении 0 индекса.

Использование id в качестве key устраняет эту проблему. Тогда React точно будет знать, что из компонентов необходимо монтировать или размонтировать. На практике это работает почти во-всех случаях реализации.

Каков путь?

Начиная с React версии 16 появился новый механизм согласовани под названием Fiber. Цель React Fiber сделать отрисовку виртуального DOM инкрементным. О нём мы и поговорим в следующей статье.

Если у вас остались вопросы, то Вы в любое время можете задать их в нашей группе VK или на канале в Telegram.