Вот краткийПередняя система знанийЖдем вас, чтобы проверить это, посмотрите, будут сюрпризы ~ Если вы думаете, что это хорошо, пожалуйста, звездочка ~
ВДОМ везде
VDOM, также известный как виртуальный DOM, не является чем-то новым. Это DOM, который существует только в памяти. Поскольку он не отображается на странице, он называется VDOM.
var a = document.createElement("div");
Как показано выше, вы должны быть знакомы с этим, верно? Да, это ВДОМ.
Вопрос в том, что если VDOM станет настоящим DOM?
На самом деле это довольно просто... просто добавьте узел на страницу
var a = document.createElement("div");
document.body.append(a);
Так что, пожалуйста, не думайте слишком сложно о VDOM! Это везде~
VDOM в React
Общие операции с DOM
Прежде чем говорить о VDOM в React, необходимо сказать, каковы общие операции DOM в нашей повседневной жизни?
На самом деле есть три категории: добавление, удаление и модификация. Соответствующие операции DOM следующие:
- Добавить узел => appendChild
- удалить узел => removeChild
- Изменить узел => replaceChild
На самом деле, многие front-end партнеры ведут себя просто и грубо, когда имеют дело с изменениями front-end шаблона.В любом случае они будут напрямую использовать jQuery-подобные html-методы для замены всего блока~ (Ищите свой код глобально, много ли там $ (...).html())
Что плохого в этом? - Проблемы с производительностью. Если страница относительно небольшая, проблема не слишком велика.Если страница огромна, она неизбежно застрянет, и пользовательский опыт определенно будет плохим.
Как это решить? - Это вводит дифференциальные обновления!
дельта-обновление
Что такое дельта-обновление? Это нужно для обновления только HTML-фрагмента ситуации. Например, если вы добавляете узел, то я обновляю только этот узел, мне не нужно заменять весь шаблон.
Таким образом повышается эффективность.
Но вопрос в том, как узнать, какой узел обновлен, какой узел удален, а какой заменен? - Нам нужно смоделировать DOM!
Моделирование ВДОМ
Проще говоря, моделирование означает использование JS-объекта для представления VDOM.
Если мы можем использовать JS-объект для представления VDOM, то у этого объекта на один атрибут больше (добавление узла), на один атрибут меньше (удаление узла) или изменилось значение атрибута (изменение узла), будет понятно с одного взгляда!
Как его смоделировать?
Это! Мы собираемся упростить. Если подумать, DOM также называют деревом DOM, которое представляет собой древовидную структуру, и в дереве DOM есть много узлов-элементов.
Нам нужно смоделировать VDOM, что, по сути, смоделировать узлы элементов один за другим, а затем вернуть узлы в указанное положение дерева DOM, чтобы моделирование дерева DOM не было завершено?
Не думайте о моделировании слишком сложно, это не что иное, как отображение его в виде объектов JS.
Как моделировать узлы элементов?
Это просто, нам нетрудно обнаружить, что каждый узел представляет собой не что иное, как составленный из следующих трех частей:
- Тип: тип элемента
- реквизит: свойства элемента
- Children : коллекция дочерних элементов
Например
Наш желаемый результат:
{type:"div",props:{"id":"main"},children:[
test
]}
Если это более сложная структура, например изображение в div, мы можем написать
{type:"div",props:{"style":""},children:[
{type:"img",props:{"src":"..."}}
]}
Результатом реакции на вышеизложенное является моделирование VDOM. Разве это не просто?
Как быстро смоделировать?
Как преобразовать реальный DOM в смоделированный VDOM?
Это просто,transform-react-jsx
Он был реализован для нас, и друзья, которые используют веб-пакет или накопительный пакет, могут использовать этот плагин напрямую.
Для справки прилагаются следующие файлы конфигурации для объединения:
import babel from 'rollup-plugin-babel';
export default {
input : 'src/main.js',
output : {
file : 'dist/main.js',
format : 'cjs'
},
banner : "/* fed123.com */",
plugins : [
babel({
'presets' : [[
'env',
{
modules : false
}
]],
"plugins" : [
["transform-react-jsx" , {
"pragma" : "vnode"
}]
]
})
]
}
Возможно, вы еще мало что знаете об этом, вот пример:
// React 常见的DOM写法
const vdom = (
<div id="_Q5" style="border:1px solid red">
<div style="text-align:center;">
<img src="https://m.baidu.com/static/index/plus/plus_logo.png" height="56"/>
</div>
Hello
</div>
);
// 转义后的
var vdom = vnode(
"div",
{ id: "_Q5", style: "border:1px solid red" },
vnode(
"div",
{ style: "text-align:center;" },
vnode("img", { src: "https://m.baidu.com/static/index/plus/plus_logo.png", height: "56", onClick: function onClick() {
alert(1);
} })
),
"Hello"
);
Как превратить VDOM в настоящий DOM?
Мы знаем, что преобразование DOM в VDOM предназначено для дифференциального обновления, и, наконец, нам нужно восстановить VDOM в DOM! ВДОМ - это просто мост, если его нельзя восстановить в ДОМ, то ВДОМ бессмысленно!
Как это сделать?
Вы можете обратиться к следующему коду:
// 把vdom挂载到页面上
function createElement(node) {
if (typeof node === 'string') {
return document.createTextNode(node);
}
const $el = document.createElement(node.type);
let appendChild = $el.appendChild.bind($el);
node.children
.map(createElement)
.map(appendChild);
return $el;
}
Мы считаем, что если дочерний узел является строковым узлом, его можно вставить непосредственно на страницу.Если дочерний узел является узлом DOM, то рекурсивно вызовите~
Благодаря этой идее мы можем восстановить VDOM в DOM.
DIFF Virtual DOM & Update
Выше приведена подготовка VDOM, которая в основном включает два этапа:
- Смоделируйте VDOM, чтобы облегчить последующие дифференциальные обновления
- Конвертировать VDOM в настоящий DOM
Далее основное блюдо.
Давайте сначала подумаем, как судить, что DOM изменился, и найти это изменение?
РАЗЛИЧНЫЙ алгоритм
Алгоритм DIFF — это подход, принятый фреймворком React. То есть определить, изменился ли DOM, а затем найти изменение, чтобы мы могли добиться дифференциальных обновлений.
Есть три основных изменения DOM: appendChild, replaceChild, removeChild.
Помните, как мы моделировали VDOM?
{type:"div",props:{"style":""},children:[
{type:"img",props:{"src":"..."}}
]}
Каждый узел содержит дочерние элементы, процесс diff на самом деле является процессом diff дочерних элементов. По рекурсивным дочерним элементам вы можете судить о разных дочерних элементах и оперировать ими. Бывают следующие ситуации:
- Если старого узла нет, создайте новый узел и вставьте родительский узел.
- Если нового узла нет, уничтожьте старый узел.
- Если узел изменился, используйте replaceChild, чтобы изменить информацию об узле.
- Если узел не меняется, то сравните дочерние узлы узла, чтобы судить, используйте рекурсивный вызов
function updateElement($parent, newNode, oldNode, index = 0) {
if(!oldNode) {
$parent.appendChild(
createElement(newNode)
);
} else if (!newNode) {
$parent.removeChild(
$parent.childNodes[index]
);
} else if (changed(newNode, oldNode)) {
$parent.replaceChild(
createElement(newNode),
$parent.childNodes[index]
);
} else if(newNode.type) {
const newLength = newNode.children.length;
const oldLength = oldNode.children.length;
for(let i = 0; i < newLength || i < oldLength; i++) {
updateElement(
$parent.childNodes[index],
newNode.children[i],
oldNode.children[i],
i
);
}
}
}
Почему разные дети? Поскольку мы должны DOM DEAL состоит из узлов элемента, наименьшая единица изменений дерева DOM также является узлом элемента.
С помощью рекурсии мы можем начать с нижних дочерних элементов, проходить слой за слоем, находить измененные узлы, а затем обновлять дельту этих узлов.
Так называемое дифференциальное обновление — это три упомянутые выше операции: appendChild, replaceChild и removeChild. Это отражено в приведенном выше коде.
Handle Props & Event
Выполняя описанные выше шаги, мы можем по-разному обновлять дерево DOM и отображать его на странице, но мы знаем, что дерево DOM имеет не только узлы, но также параметры и события, поэтому нам нужно добавить параметры и события.
Взгляните еще раз на нашу модельку ВДОМа!
{type:"div",props:{"style":""},children:[
{type:"img",props:{"src":"..."}}
]}
Что нам нужно сделать, так это загрузить реквизиты в соответствующий узел элемента, этот шаг называется: реквизиты DIFF.
DIFF props, как и DIFF VDOM, находит разницу в props, затем setAttribute и removeAttribute.
Код прямо здесь:
function updateProps ($target, newProps, oldProps = {}){
const props = Object.assign({},oldProps, newProps);
Object.keys(props).forEach(name => {
updateProp($target, name, newProps[name], oldProps[name]);
})
}
function updateProp ($target, name, newVal, oldVal) {
if (!newVal) {
removeProp($target, name, oldVal);
} else if (!oldVal || newVal !== oldVal) {
setProp($target, name, newVal);
}
}
function setProp ($target, name, value) {
if (typeof value === "boolean") {
handleBooleanProp($target, name, value);
}
$target.setAttribute(name, value);
}
function setBooleanProp($target, name, value) {
if (!!value) {
$target.setAttribute(name, value);
$target[name] = true;
} else {
$target[name] = false;
}
}
function removeProp($target, name, value) {
if (typeof value === 'boolean') {
$target[name] = false;
}
$target.removeAttribute(name);
}