Приветствую Вас, дорогие читатели Хабр! В данной статье мы рассмотрим работу DOM на таких JavaScript библиотеках, как Vue, React, Angular. Материал поможет понять принцип работы, конечно, самый лучший способ разобраться в той или иной теме — это практика. В этой статье я привел несколько примеров для лучшего понимания материала.
Real DOM vs Shadow DOM vs Virtual DOM
Перед сравнением работы DOM на определенной библиотеке. Давайте рассмотрим, какие DOM есть, чем они отличаются, рассмотрим примеры.
1. Real DOM, или проще говоря DOM, обозначает объектную модель документа, и в это время представление в памяти нашего HTML в виде объектов. У нас есть несколько элементов нашего HTML, представленных в виде вложенных узлов, показанных в форме дерева. На картинке можете увидеть наглядный пример:
Для управления поведением узла нашего дерева мы используем querySelector. document.querySelector получает элемент из дерева и далее с помощью атрибутов мы можем работать над поведением.
2. Shadow DOM – это относительно новая функция DOM, которая позволяет вам создавать собственные компоненты. Например, кнопки воспроизведения и паузы, которые мы видим на странице, отсутствуют в Real DOM.
Видео-компонент – это пользовательский компонент, который был создан Chrome с использованием Shadow DOM. Chrome не отображает Shadow DOM по умолчанию, но мы можем включить в настройках. После включения в узле-родителе video мы видим дочерние узлы.
Все js-скрипты в данном примере скрыты от пользователя. Chrome делает работы за нас. Еще одно преимущество Shadow DOM заключается в том, что CSS-стили не влияют на дочерние узлы пользовательского компонента, который предоставляет Chrome. Такие пользовательские компоненты мы тоже можем написать.
3. Virtual DOM – абстракция поверх существующего DOM. То есть, у нас при изменении конкретных элементов дерева обновляется не всё дерево, а только измененные нами элементы. Проще говоря, идет сравнение между Real DOM и Virtual DOM. Такой процесс называется согласованием.
Сравнение принципа работы на библиотеках JavaScript
React использует паттерн проектирования "Наблюдатель" (observer). При изменении состояния компонента, React обновляет VDOM. После обновления VDOM, React сравнивает его текущую версию с предыдущей. Этот процесс называется "поиском различий" (diffing).
React и Vue примерно одинаково работают, они используют Virtual DOM. Но есть несколько отличий. По сравнению с React — Vue, напротив, использует алгоритм "обновления на месте" (in-place patching), который обновляет виртуальное DOM на месте без полного сравнения со старым состоянием.
React использует систему "подъема состояния" (state lifting) и "контекст" (context) для отслеживания изменений, тогда как Vue предоставляет систему наблюдателей (observers) и реактивных свойств (reactive properties). Vue также предлагает вычисляемые свойства (computed properties) и наблюдаемые свойства (watchers) для более удобной работы с реактивностью.
Пример реализации на Vue:
Родительский компонент
<template>
<div>
<child-component :count="count" @increment="incrementCount" />
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
data() {
return {
count: 0
};
},
methods: {
incrementCount() {
this.count++;
}
}
};
</script>
Дочерний компонент
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="$emit('increment')">Increment</button>
</div>
</template>
<script>
export default {
props: ['count']
};
</script>
Пример реализации на React:
Родительский компонент
import { useState, createContext } from 'react';
import ChildComponent from './ChildComponent';
const MyContext = createContext();
const ParentComponent = () => {
const [count, setCount] = useState(0);
return (
<div>
<MyContext.Provider value={{ count, setCount }}>
<ChildComponent />
</MyContext.Provider>
</div>
);
};
export default ParentComponent;
Дочерний компонент
import { useContext } from 'react';
import MyContext from './MyContext';
const ChildComponent = () => {
const { count, setCount } = useContext(MyContext);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
export default ChildComponent;
Angular, в отличие от Vue и React, использует Incremental DOM. Incremental DOM используется компанией Google для внутренних нужд. Его основная идея такова: "Каждый компонент компилируется в набор инструкций, которые создают DOM-деревья и непосредственно обновляют их при изменении данных".
Например, код компонента, который мы видим ниже:
Компонент
@Component({
selector: 'todos-cmp',
template: `
<div *ngFor="let t of todos|async">
{{t.description}}
</div>
`
})
class TodosComponent {
todos: Observable<Todo[]> = this.store.pipe(select('todos'));
constructor(private store: Store<AppState>) {}
}
Будет скомпилирован в:
Скомпилированный компонент
var TodosComponent = /** @class */ (function () {
function TodosComponent(store) {
this.store = store;
this.todos = this.store.pipe(select('todos'));
}
TodosComponent.ngComponentDef = defineComponent({
type: TodosComponent,
selectors: [["todos-cmp"]],
factory: function TodosComponent_Factory(t) {
return new (t || TodosComponent)(directiveInject(Store));
},
consts: 2,
vars: 3,
template: function TodosComponent_Template(rf, ctx) {
if (rf & 1) { // create dom
pipe(1, "async");
template(0, TodosComponent_div_Template_0, 2, 1, null, _c0);
} if (rf & 2) { // update dom
elementProperty(0, "ngForOf", bind(pipeBind1(1, 1, ctx.todos)));
}
},
encapsulation: 2
});
return TodosComponent;
}());
Данный пример взял из этой статьи.
Инкрементальный DOM в Angular, по сравнению с Vue и React, напротив, использует более прямой подход, обновляя только те части реального DOM, которые фактически изменились, без необходимости сравнивать целые деревья виртуального DOM.
Инкрементальный DOM в Angular часто считается более эффективным с точки зрения производительности, поскольку он применяет изменения напрямую к реальному DOM без дополнительных вычислительных затрат на сравнение деревьев виртуального DOM
Компания Google и поставила перед собой задачу — сделать так, чтобы приложения показывали хорошую производительность на мобильных устройствах. А значит, была необходима оптимизация размера бандла и объёма потребляемой памяти.
Подводя итоги, мы поверхностно рассмотрели, как используется DOM на определенной библиотеке. Для общего понятия принципа работы хорошо подойдет новичкам. Понимание какой-либо технологии, алгоритма приходит со временем. Надеюсь, статья помогла разобраться в этой области.