От создателя: борьба с никчемными манипуляциями с DOM, согласование и метод различий.
Настоящая DOM
Перво-наперво, DOM значит «объектную модель документа». Ординарными словами DOM представляет пользовательский интерфейс вашего приложения. Всякий раз, когда происходит изменение состояния пользовательского интерфейса вашего приложения, DOM обновляется, чтоб представить это изменение. Сейчас загвоздка состоит в том, что нередкие манипуляции с DOM влияют на производительность, делая ее неспешной.
Что замедляет манипуляции с DOM?
Модель DOM представлена в виде древовидной структуры данных. Из-за этого конфигурации и обновления в DOM происходят стремительно. Но опосля конфигурации освеженный элемент и его дочерние элементы должны быть повторно отрисованы, чтоб обновить пользовательский интерфейс приложения. Повторный рендеринг либо перерисовка пользовательского интерфейса — вот что делает его неспешным. Как следует, чем больше у вас компонент пользовательского интерфейса, тем дороже могут быть обновления DOM, так как их необходимо будет повторно показывать при любом обновлении DOM.
Манипуляции с DOM — это сердечко современного интерактивного Веба. К огорчению, это также намного медлительнее, чем большая часть операций JavaScript. Эта медлительность утежеляется тем фактом, что большая часть фреймворков JavaScript обновляют DOM еще почаще, чем нужно.
В качестве примера представим, что у вас есть перечень из 10 частей. Вы отмечаете 1-ый пункт. Большая часть фреймворков JavaScript перестраивают весь перечень. Это в 10 раз больше работы, чем нужно! Поменялся лишь один элемент, а другие девять будут восстановлены буквально таковыми же, по-прежнему.
Перестроение перечня не представляет огромного труда для веб-браузера, но современные сайты могут употреблять большущее количество манипуляций с DOM. Неэффективное обновление сделалось суровой неувязкой. Чтоб решить эту делему, люди которые работают с React популяризировали нечто, называемое виртуальным DOM.
Виртуальная DOM
В React для всякого объекта DOM существует соответственный «виртуальный объект DOM». Виртуальный объект DOM — это представление объекта DOM, подобно его облегченной копии. Виртуальный объект DOM имеет те же характеристики, что и настоящий объект DOM, но у него нет настоящей способности впрямую изменять то, что отображается на дисплее.
«Виртуальная модель DOM (VDOM) — это теория программирования, в какой безупречное либо «виртуальное» представление пользовательского интерфейса хранится в памяти и синхронизируется с« настоящей » библиотекой DOM, таковой как ReactDOM. Этот процесс именуется согласованием».
Манипулирование DOM происходит медлительно. Управление виртуальным DOM происходит намного резвее, поэтому что на дисплее ничего не отображается. Думайте о манипулировании виртуальной DOM как о редактировании чертежа, а не о перемещении комнат в настоящем доме.
Как виртуальный DOM работает резвее?
Когда в пользовательский интерфейс добавляются новейшие элементы, создается виртуальная модель DOM, представленная в виде дерева. Любой элемент является узлом в этом дереве. Если состояние хоть какого из этих частей меняется, создается новое виртуальное дерево DOM. Потом это дерево сравнивается с предшествующим виртуальным деревом DOM.
Как это будет изготовлено, виртуальная DOM вычисляет лучший из вероятных способов внесения этих конфигураций в настоящую DOM. Это гарантирует малое количество операций с настоящей DOM. Как следует, понижение цены обновления настоящей модели DOM.
На изображении ниже показано виртуальное дерево DOM и процесс сопоставления.
Красноватые кружки представляют собой узлы, которые поменялись. Эти узлы представляют элементы пользовательского интерфейса, состояние которых было изменено. Потом рассчитывается разница меж предшествующей версией виртуального дерева DOM и текущим виртуальным деревом DOM. Потом все родительское поддерево повторно визуализируется для получения освеженного пользовательского интерфейса. Это освеженное дерево потом пакетно обновляется до настоящей модели DOM.
Как React употребляет виртуальную модель DOM?
Сейчас, когда у вас есть точное представление о том, что такое виртуальная модель DOM и как она может повысить производительность вашего приложения, давайте поглядим, как React употребляет виртуальную модель DOM.
1. React следует шаблону наблюдающему и выслеживает конфигурации состояния.
В React любая часть пользовательского интерфейса является компонентом, и любой компонент имеет состояние. Когда состояние компонента меняется, React обновляет виртуальное дерево DOM. Опосля обновления виртуальной DOM, React ассоциирует текущую версию виртуальной DOM с предшествующей версией виртуальной DOM. Этот процесс именуется «дифференцированием».
Яндекс представил масштабное обновление поисковой системы
Как React выяснит, какие виртуальные объекты DOM были изменены, он обновляет лишь эти объекты в настоящей DOM. Это существенно увеличивает производительность по сопоставлению с конкретным манипулированием настоящей DOM. Это выделяет React как высокопроизводительную библиотеку JavaScript.
2. React следует механизму пакетного обновления для обновления настоящей DOM.
Как следует — приводит к повышению производительности. Это значит, что обновления настоящей модели DOM отправляются пакетами заместо отправки обновлений для всякого отдельного конфигурации состояния.
Перекраска пользовательского интерфейса — самая дорогостоящая часть, и React гарантирует, что настоящий DOM получает лишь пакетные обновления для перерисовки пользовательского интерфейса.
3. React следует действенному методу различий.
React реализует эвристический метод O (n), основанный на 2-ух догадках:
Два элемента различных типов будут давать различные деревья.
Разраб может установить, какие дочерние элементы могут быть размеренными при различных рендерингах при помощи характеристики key.
На практике эти догадки верны практически для всех случаев использования. При рассмотрении 2-ух деревьев React поначалу ассоциирует два корневых элемента. Поведение различается зависимо от типов корневых частей.
Элементы различных типов
Каждый раз, когда корневые элементы имеют различные типы, React удаляет старенькое дерево и строит новое дерево с нуля.
При удалении дерева старенькые узлы DOM уничтожаются. Экземпляры компонент получают состояние componentWillUnmount(). При построении новейшего дерева новейшие узлы DOM вставляются в DOM. Экземпляры компонент получают состояние UNSAFE_componentWillMount() а потом componentDidMount(). Хоть какое состояние, связанное со старенькым деревом, пропадает.
Любые составляющие ниже корневого также будут размонтированы, и их состояние будет уничтожено. Пример, для сопоставления:
1234567 |
Это убьет старенькый Counter и перемонтирует новейший.
Элементы 1-го типа
При сопоставлении 2-ух частей React DOM 1-го типа React глядит на атрибуты обоих, сохраняет один и этот же базисный узел DOM и обновляет лишь модифицированные атрибуты. К примеру:
1 |
Сравнивая эти два элемента, React понимает, что необходимо изменять лишь базисный узел className. При обновлении style, React также понимает, что необходимо обновлять лишь те характеристики, которые поменялись. К примеру:
12 |
При преобразовании меж этими 2-мя элементами React понимает, что необходимо изменять лишь стиль color, а не fontWeight. Опосля обработки узла DOM React рекурсивно обращается к дочерним элементам.
Рекурсия по дочерним элементам
По дефлоту при рекурсии по дочерним элементам узла DOM, React просто делает итерацию по обоим перечням дочерних частей сразу и генерирует мутацию каждый раз, когда есть разница. К примеру, при добавлении элемента в конце дочерних частей преобразование меж этими 2-мя деревьями работает отлично:
- first
- second
- first
- second
- third
123456789 |
|
React соотнесет два дерева
, соотнесет два дерева
, а потом вставит дерево
.
Если вы реализуете это без Virtual DOM, вставка элемента сначала имеет худшую производительность. К примеру, преобразование меж этими 2-мя деревьями работает плохо:
- Duke
- Villanova
- Connecticut
- Duke
- Villanova
123456789 |
|
React будет модифицировать любой дочерний элемент заместо того, чтоб взять во внимание, что он может сохранить нетронутыми поддеревья
и
. Эта неэффективность может стать неувязкой.
Внедрение ключей
Чтоб решить эту делему, React поддерживает атрибут key. Когда у потомков есть ключи, React употребляет ключ для сравнения их в начальном дереве с потомками в следующем дереве. К примеру, добавление key к нашему неэффективному примеру выше в состоянии сделать преобразование дерева действенным:
- Duke
- Villanova
- Connecticut
- Duke
- Villanova
123456789 |
|
Сейчас React понимает, что элемент с ключом ’2014′ является новеньким, а элементы с ключами ’2015′ и ’2016′ лишь переместились.
На практике отыскать ключ обычно легко. Элемент, который вы собираетесь показывать, может уже иметь неповторимый идентификатор, потому ключ быть может просто получен из ваших данных:
1 |
|
Если это не так, вы сможете добавить новое свойство ID в свою модель либо хешировать некие части контента для генерации ключа. Ключ должен быть неповторимым лишь посреди собственных братьев и сестер, а не глобально.
В последнем случае вы сможете передать индекс элемента в массиве в качестве ключа. Это может сработать, если элементы никогда не переупорядочиваются, но переупорядочивание будет неспешным.
Изменение порядка также может вызвать препядствия с состоянием компонента, когда индексы употребляются в качестве ключей. Экземпляры компонент обновляются и повторно употребляются зависимо от их ключа. Если ключ является индексом, перемещение элемента изменяет его. В итоге состояние компонент для таковых вещей, как неконтролируемые вводы, может смешиваться и обновляться нежданным образом.
Проще говоря: «Вы сообщаете React, в котором состоянии вы желаете, чтоб находился пользовательский интерфейс, и он гарантирует, что DOM соответствует этому состоянию. Огромным преимуществом тут будет то, что для вас как разрабу не надо знать, как созодать манипуляции с атрибутами, обработку событий либо ручное обновление DOM за кулисами».
Все эти детали абстрагируются от разрабов React. Все, что для вас необходимо создать, это обновить состояния вашего компонента по мере необходимости, а React позаботится обо всем остальном. Это обеспечивает потрясающую производительность разраба при использовании React.
Так как «виртуальный DOM» — это быстрее шаблон, чем определенная разработка, люди время от времени молвят, что это значит различные вещи. В мире React термин «виртуальный DOM» обычно ассоциируется с элементами React, так как они являются объектами, представляющими пользовательский интерфейс. Но React также употребляет внутренние объекты, именуемые «волокнами», для хранения доборной инфы о дереве компонент. Их также можно разглядывать как часть реализации «виртуальной DOM» в React. Fiber — это новейший механизм согласования в React 16. Его основная цель — включить инкрементный рендеринг виртуальной DOM.
Как смотрится виртуальная модель DOM?
Заглавие «виртуальный DOM», обычно, добавляет загадочности тому, что же это все-таки за теория по сути. Практически, виртуальная модель DOM — это просто обыденный объект Javascript. Вернемся к дереву DOM, которое мы сделали ранее:
Это дерево также быть может представлено как объект Javascript.
JavaScript const vdom = { tagName: «html», children: [ { tagName: «head» }, { tagName: «body», children: [ { tagName: «ul», attributes: { «class»: «list» }, children: [ { tagName: «li», attributes: { «class»: «list__item» }, textContent: «List item» } // end li ] } // end ul ] } // end body ]
} // end html
12345678910111213141516171819202122 | const vdom = { tagName: «html», children: [ { tagName: «head» }, { tagName: «body», children: [ { tagName: «ul», attributes: { «class»: «list» }, children: [ { tagName: «li», attributes: { «class»: «list__item» }, textContent: «List item» } // end li ] } // end ul ] } // end body ]} // end html |
Мы можем мыслить о этом объекте как о нашем виртуальном DOM. Как и начальная модель DOM, это объектное представление нашего HTML-документа. Но так как это обычный объект Javascript, мы можем свободно и нередко манипулировать им, не касаясь фактического DOM до того времени, пока нам это не пригодится.
Заместо использования всего объекта обычно работают с маленькими участками виртуальной DOM. К примеру, мы можем работать над компонентом list, который будет соответствовать нашему элементу неупорядоченного перечня.
JavaScript const list = { tagName: «ul», attributes: { «class»: «list» }, children: [ { tagName: «li», attributes: { «class»: «list__item» }, textContent: «List item» } ]
};
1234567891011 | const list = { tagName: «ul», attributes: { «class»: «list» }, children: [ { tagName: «li», attributes: { «class»: «list__item» }, textContent: «List item» } ]}; |
Под капотом виртуальной DOM
Сейчас, когда мы узрели, как смотрится виртуальная модель DOM, как она работает для решения заморочек производительности и удобства использования модели DOM?
Как я уже упоминал, мы можем употреблять виртуальную DOM, чтоб выделить определенные конфигурации, которые нужно внести в DOM, и создать эти определенные обновления без помощи других. Вернемся к нашему примеру с неупорядоченным перечнем и внесем те же конфигурации, которые мы сделали при помощи DOM API.
1-ое, что мы создадим, — это создадим копию виртуальной DOM, содержащую конфигурации, которые мы желаем внести. Так как нам не надо употреблять DOM API, мы можем просто сделать новейший объект.
JavaScript const copy = { tagName: «ul», attributes: { «class»: «list» }, children: [ { tagName: «li», attributes: { «class»: «list__item» }, textContent: «List item one» }, { tagName: «li», attributes: { «class»: «list__item» }, textContent: «List item two» } ]
};
12345678910111213141516 | const copy = { tagName: «ul», attributes: { «class»: «list» }, children: [ { tagName: «li», attributes: { «class»: «list__item» }, textContent: «List item one» }, { tagName: «li», attributes: { «class»: «list__item» }, textContent: «List item two» } ]}; |
Подробное рассмотрение CSS Grid minmax()
Команда copy употребляется для сотворения так именуемой «различия» меж начальной виртуальной DOM, в этом случае list, и освеженной. Дифференциал может смотреться приблизительно так:
JavaScript const diffs = [ { newNode: { /* new version of list item one */ }, oldNode: { /* original version of list item one */ }, index:/* index of element in parent’s list of child nodes */ }, { newNode: { /* list item two */ }, index: { /* */ } }
]
1234567891011 | const diffs = [ { newNode: { /* new version of list item one */ }, oldNode: { /* original version of list item one */ }, index:/* index of element in parent’s list of child nodes */ }, { newNode: { /* list item two */ }, index: { /* */ } }] |
Diff предоставляет аннотации по обновлению фактического DOM. Как все различия будут собраны, мы можем заносить конфигурации в DOM, делая лишь те обновления, которые нужны.
К примеру, мы могли бы перебрать любой diff и или добавить новейший дочерний элемент, или обновить старенькый, зависимо от того, что обозначено в diff.
JavaScript const domElement = document.getElementsByClassName(«list»)[0]; diffs.forEach((diff) => { const newElement = document.createElement(diff.newNode.tagName);
/* Add attributes … */
if (diff.oldNode) { // If there is an old version, replace it with the new version domElement.replaceChild(diff.newNode, diff.index); } else { // If no old version exists, create a new node domElement.appendChild(diff.newNode); }
})
12345678910111213 | const domElement = document.getElementsByClassName(«list»)[0];diffs.forEach((diff) => { const newElement = document.createElement(diff.newNode.tagName); /* Add attributes … */ if (diff.oldNode) { // If there is an old version, replace it with the new version domElement.replaceChild(diff.newNode, diff.index); } else { // If no old version exists, create a new node domElement.appendChild(diff.newNode); }}) |
Направьте внимание, что это вправду облегченная и урезанная версия того, как может работать виртуальный DOM, и есть много случаев, которые я тут не разглядел.
Виртуальный DOM и фреймворки
Принято работать с виртуальной DOM через фреймворк, а не впрямую вести взаимодействие с ней, как я показал в примере выше.
Такие фреймворки, как React и Vue, употребляют теорию виртуальной DOM для наиболее производительных обновлений DOM. К примеру, наш компонент list можно написать на React последующим образом.
JavaScript import React from ‘react’; import ReactDOM from ‘react-dom’; const list = React.createElement(«ul», { className: «list» }, React.createElement(«li», { className: «list__item» }, «List item») );
ReactDOM.render(list, document.body);
123456 | import React from ‘react’;import ReactDOM from ‘react-dom’;const list = React.createElement(«ul», { className: «list» }, React.createElement(«li», { className: «list__item» }, «List item»));ReactDOM.render(list, document.body); |
Если б мы желали обновить наш перечень, мы могли бы просто переписать весь шаблон перечня и вызвать ReactDOM.render() опять, передав новейший перечень.
JavaScript const newList = React.createElement(«ul», { className: «list» }, React.createElement(«li», { className: «list__item» }, «List item one»), React.createElement(«li», { className: «list__item» }, «List item two»); );
setTimeout(() => ReactDOM.render(newList, document.body), 5000);
12345 | const newList = React.createElement(«ul», { className: «list» }, React.createElement(«li», { className: «list__item» }, «List item one»), React.createElement(«li», { className: «list__item» }, «List item two»););setTimeout(() => ReactDOM.render(newList, document.body), 5000); |
Так как React употребляет виртуальную модель DOM, даже невзирая на то, что мы повторно визуализируем весь шаблон, обновляются лишь те части, которые вправду изменяются. Если мы поглядим на наши инструменты разраба, когда произойдет изменение, мы увидим определенные элементы и их части, которые изменяются.
Заключение
Виртуальная DOM точно просуществует некое время. Она предоставляет вправду неплохой метод отделить логику приложения от частей DOM и, как следует, понижает возможность сотворения ненамеренных заморочек, когда дело доходит до манипуляций с ней. Остальные библиотеки продвигаются вперед с этим же подходом, еще более уплотняя эту теорию как одну из желательных стратегий для веб-приложений.
Применяемый подход Angular, который, может быть, является фреймворком, который популяризировал теорию SPA (одностраничных приложений), именуется Dirty Model Checking. Необходимо отметить, что dirty model checking и virtual DOM это не взаимоисключающие вещи. Оба они являются решением одной и той же препядствия, но решают ее по-разному. Структура MVC полностью может воплотить оба способа. В случае с React это просто не имело особенного смысла — React — это по большей части библиотека для отображения Представления.
Надеюсь, что благодаря этому вы почувствуете себя наиболее уютно с «Virtual DOM». Спасибо за чтение.
Создатель: Ayush Verma
Редакция: Команда webformyself.
Читайте нас в Telegram, VK, Yandex.Дзен
Источник
Вам также может понравиться