-
Notifications
You must be signed in to change notification settings - Fork 46
Virtual DOM
- Virtual DOM это любой вид представления реального DOM (any kind of representation of a real DOM).
- Когда мы что-то изменяем в нашем виртуальном DOM-дереве, мы получаем новое виртуальное дерево. Алгоритм сравнивает старое и новое деревья, находит их различия и делает лишь необходимые изменения в реальном DOM. Что и отражает слово virtual.
Итак, для начала нам нужно как-то хранить исходное дерево DOM в памяти. Для примера:
<ul class=”list”>
<li>item 1</li>
<li>item 2</li>
</ul>Для этого подойдут обычные JS-объекты:
{
type: ‘ul’,
props: { ‘class’: ‘list’ },
children: [
{ type: ‘li’, props: {}, children: [‘item 1’] },
{ type: ‘li’, props: {}, children: [‘item 2’] }
]
}Здесь мы представляем DOM узлы-элементы как объекты. Будем представлять текстовые DOM-узлы как обычные JS-строки.
Для рассмотрения общего случая нам понадобится вспомогательная функция (helper):
const h = (type, props, ...children) => ({ type, props, children });С её помощью мы можем переписать код так:
h(
'ul',
{ 'class' : 'list' },
h('li', {}, 'item 1'),
h('li', {}, 'item 2'),
);Уже лучше, но можно пойти дальше. Как работает JSX? Babel транслирует код (transpile) во что-то такое:
React.createElement(
‘ul’,
{ className: ‘list’ },
React.createElement(‘li’, {}, ‘item 1’),
React.createElement(‘li’, {}, ‘item 2’),
);Что очень похоже на нашу вспомогательную функцию. Чтобы сделать что-то подобное, мы можем использовать jsx pragma. Для этого нужно просто над исходным HTML кодом оставить комментарий вида:
/* @jsx h */В этом случае мы укажем Babel'ю, что нужно транслировать этот JSX, но вместо React.createElement() использовать нашу вспомогательную функцию h().
Таким образом код
/* @jsx h */
const d = (<div>Hello</div>);транслируется Babel'ем в код:
const d = h('div', {});А после выполнения вспомогательной функции получим простые JS объекты - наше представление Virtual DOM:
const d = ({ type: 'div', props: {}, children: [] });У нас уже есть представление дерева DOM со своей структурой в виде объектов. Теперь нам нужно создать реальный DOM по этой структуре.
Cделаем несколько предположений и установим терминологию:
- Реальные DOM узлы (текстовые и элементы) будем обозначать со знаком $.
- Представление Virtual DOM будет располагаться в переменной node.
- Будет использоваться только один корневой компонент (root), как в React.
Напишем функцию createElement(node), принимающую узел Virtual DOM и возвращающую узел реального DOM:
const createElement = node => (typeof node === ‘string’)
? document.createTextNode(node)
: document.createElement(node.type);Подумаем теперь о **дочерних элементах **(children). Так как они являются узлами, то их так же можно создать с помощью createElement(node). Получается рекурсия. Затем добавляем их с помощью appendChild. Таким образом к функция примет вид:
const createElement = node => {
if (typeof node === ‘string’) {
return document.createTextNode(node);
}
const $elem = document.createElement(node.type);
node.children
.map(createElement)
.forEach($elem.appendChild.bind($elem));
return $elem;
};Сейчас мы можем превращать наш Virtual DOM в реальный DOM. Теперь построим алгоритм сравнения старого и нового виртуальных деревьев, а затем изменим необходимое в реальном DOM.
Могут быть следующие случаи:
- appendChild() - добавление узла. В старом дереве отсутствует узел, который есть в новом дереве на том же месте.
- removeChild() - удаление узла. В новом дереве отсутствует узел, который есть в старом дереве на том же месте.
- replaceChild() - замена узла. Узлы на одном и том же месте в старом и новом деревьях не совпадают.
- Ничего не делаем. Узлы совпадают. Идём искать дальше.