виртуальный DOM

DOM

Вот краткийПередняя система знанийЖдем вас, чтобы проверить это, посмотрите, будут сюрпризы ~ Если вы думаете, что это хорошо, пожалуйста, звездочка ~


ВДОМ везде

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 следующие:

  1. Добавить узел => appendChild
  2. удалить узел => removeChild
  3. Изменить узел => replaceChild

На самом деле, многие front-end партнеры ведут себя просто и грубо, когда имеют дело с изменениями front-end шаблона.В любом случае они будут напрямую использовать jQuery-подобные html-методы для замены всего блока~ (Ищите свой код глобально, много ли там $ (...).html())

Что плохого в этом? - Проблемы с производительностью. Если страница относительно небольшая, проблема не слишком велика.Если страница огромна, она неизбежно застрянет, и пользовательский опыт определенно будет плохим.

Как это решить? - Это вводит дифференциальные обновления!

дельта-обновление

Что такое дельта-обновление? Это нужно для обновления только HTML-фрагмента ситуации. Например, если вы добавляете узел, то я обновляю только этот узел, мне не нужно заменять весь шаблон.

Таким образом повышается эффективность.

Но вопрос в том, как узнать, какой узел обновлен, какой узел удален, а какой заменен? - Нам нужно смоделировать DOM!

Моделирование ВДОМ

Проще говоря, моделирование означает использование JS-объекта для представления VDOM.

Если мы можем использовать JS-объект для представления VDOM, то у этого объекта на один атрибут больше (добавление узла), на один атрибут меньше (удаление узла) или изменилось значение атрибута (изменение узла), будет понятно с одного взгляда!

Как его смоделировать?

Это! Мы собираемся упростить. Если подумать, DOM также называют деревом DOM, которое представляет собой древовидную структуру, и в дереве DOM есть много узлов-элементов.

Нам нужно смоделировать VDOM, что, по сути, смоделировать узлы элементов один за другим, а затем вернуть узлы в указанное положение дерева DOM, чтобы моделирование дерева DOM не было завершено?

Не думайте о моделировании слишком сложно, это не что иное, как отображение его в виде объектов JS.

Как моделировать узлы элементов?

Это просто, нам нетрудно обнаружить, что каждый узел представляет собой не что иное, как составленный из следующих трех частей:

  1. Тип: тип элемента
  2. реквизит: свойства элемента
  3. Children : коллекция дочерних элементов

Например

test
, тип — div, реквизит — id="main", дети — "test".

Наш желаемый результат:

{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, которая в основном включает два этапа:

  1. Смоделируйте VDOM, чтобы облегчить последующие дифференциальные обновления
  2. Конвертировать VDOM в настоящий DOM

Далее основное блюдо.

Давайте сначала подумаем, как судить, что DOM изменился, и найти это изменение?

РАЗЛИЧНЫЙ алгоритм

Алгоритм DIFF — это подход, принятый фреймворком React. То есть определить, изменился ли DOM, а затем найти изменение, чтобы мы могли добиться дифференциальных обновлений.

Есть три основных изменения DOM: appendChild, replaceChild, removeChild.

Помните, как мы моделировали VDOM?

{type:"div",props:{"style":""},children:[
        {type:"img",props:{"src":"..."}}
    ]}

Каждый узел содержит дочерние элементы, процесс diff на самом деле является процессом diff дочерних элементов. По рекурсивным дочерним элементам вы можете судить о разных дочерних элементах и ​​оперировать ими. Бывают следующие ситуации:

  1. Если старого узла нет, создайте новый узел и вставьте родительский узел.
  2. Если нового узла нет, уничтожьте старый узел.
  3. Если узел изменился, используйте replaceChild, чтобы изменить информацию об узле.
  4. Если узел не меняется, то сравните дочерние узлы узла, чтобы судить, используйте рекурсивный вызов

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);
}

Адрес исходного кода проекта