从头创建您自己的vuei .js——第3部分(构建VDOM)

从头创建您自己的vuei .js——第3部分(构建VDOM)

如果你喜欢这篇文章,你可能也会喜欢我的推特。如果你很好奇,可以看看我的Twitter简介。????

这是“从头创建您自己的vuei .js”系列文章的第三部分,在这里我将教您如何创建响应式框架(比如vuei .js)的基础知识。要阅读这篇博客文章,我建议您阅读本系列的第一部分和第二部分。

这篇文章一开始可能很长,但可能不像它看起来那么专业。它描述了代码的每一步,这就是为什么它看起来很复杂的原因。但容忍我,所有这一切将在最后????完美的意义

Building the Virtual DOM

The skeleton

在本系列的第2部分中,我们了解了虚拟DOM如何工作的基础知识。从要点的最后一点复制VDOM框架。我们使用该代码进行跟踪。您还可以在那里找到VDOM引擎的完成版本。我还创建了一个Codepen,您可以在其中使用它。

Creating a virtual node

因此,要创建一个虚拟节点,我们需要标签、属性和子节点。我们的函数是这样的:

function h(tag, props, children){ ... }

(在Vue中,创建虚拟节点的函数命名为h,这就是我们在这里的调用方式。) 在这个函数中,我们需要一个以下结构的JavaScript对象。

{
    tag: 'div',
    props: {
        class: 'container'
    },
    children: ...
}

要实现这一点,我们需要在一个对象中包装标签、属性和子节点参数并返回:

function h(tag, props, children) {
    return {
        tag,
        props,
        children,
    }
}

这就是虚拟节点创建的全部内容。

Mount a virtual node to the DOM

我将虚拟节点挂载到DOM的意思是,将其附加到任何给定的容器。这个节点可以是原始容器(在我们的示例中是#app-div),也可以是另一个虚拟节点(例如,在

这将是一个递归函数,因为我们必须遍历所有节点的子节点并将其挂载到各自的容器中。

我们的挂载函数看起来像这样:

function mount(vnode, container) { ... }

我们需要创建一个DOM元素

const el = (vnode.el = document.createElement(vnode.tag))

2)我们需要将属性(道具)设置为DOM元素的属性:

我们通过迭代它们来做到这一点,像这样:

for (const key in vnode.props) {
    el.setAttribute(key, vnode.props[key])
}

3)我们需要装载元素中的子元素

记住,有两种类型的孩子:

一个简单的文本

虚拟节点数组

我们处理:

// Children is a string/text
if (typeof vnode.children === 'string') {
    el.textContent = vnode.children
}

// Chilren are virtual nodes
else {
    vnode.children.forEach(child => {
        mount(child, el) // Recursively mount the children
    })
}

在这段代码的第二部分中可以看到,使用相同的挂载函数挂载子线程。这将递归地继续,直到只剩下“文本节点”。然后递归停止。

作为挂载函数的最后一部分,我们需要将创建的DOM元素添加到相应的容器中:

container.appendChild(el)

Unmount a virtual node from the DOM

在卸载函数中,我们从实际DOM中的父节点中删除给定的虚拟节点。该函数仅将虚拟节点作为参数。

function unmount(vnode) {
    vnode.el.parentNode.removeChild(vnode.el)
}

Patch a virtual node

这意味着使用两个虚拟节点,比较它们,并找出它们之间的区别。

到目前为止,这是我们将为虚拟DOM编写的最广泛的函数,不过请耐心听我说。

1)分配我们将要使用的DOM元素

const el = (n2.el = n1.el)

2)检查节点是否属于不同的标签

如果节点具有不同的标记,我们可以假设内容完全不同,只需完全替换节点即可。我们通过挂载新节点并卸载旧节点来实现这一点。

if (n1.tag !== n2.tag) {
    // Replace node
    mount(n2, el.parentNode)
    unmount(n1)
} else {
    // Nodes have different tags
}

如果节点具有相同的标签;但是,它可以表示两种不同的意思:

新节点有字符串子节点

新节点有一组子节点

一个节点有字符串子节点的情况

在本例中,我们将继续使用“children”(实际上只是一个字符串)替换元素的textContent。

...
    // Nodes have different tags
    if (typeof n2.children === 'string') {
        el.textContent = n2.children
    }
...

4)如果节点有一组子节点

在这种情况下,我们必须检查孩子们之间的差异。有三种情况:

子结点的长度是一样的

旧节点比新节点有更多的子节点。在这种情况下,我们需要从DOM中删除“exceed”子节点

新节点比旧节点有更多的子节点。在本例中,我们需要向DOM添加额外的子元素。

所以首先,我们需要确定子节点的公共长度,或者换句话说,每个节点的子节点的最小值:

const c1 = n1.children
const c2 = n2.children
const commonLength = Math.min(c1.length, c2.length)

5) Patch common children

对于从点4)开始的每一种情况,我们需要修补节点共有的子节点:

for (let i = 0; i < commonLength; i++) {
    patch(c1[i], c2[i])
}

在长度相等的情况下,这已经是它了。没有什么可做的了。

6) Remove unneeded children from the DOM

如果新节点的子节点比旧节点少,则需要从DOM中删除这些子节点。我们已经为此编写了unmount函数,所以现在我们需要遍历额外的子节点并卸载它们:

if (c1.length > c2.length) {
    c1.slice(c2.length).forEach(child => {
        unmount(child)
    })
}

7) Add additional children to the DOM

如果新节点比旧节点有更多的子节点,我们需要将它们添加到DOM中。我们已经为此编写了mount函数。现在我们需要遍历额外的子节点并挂载它们:

else if (c2.length > c1.length) {
    c2.slice(c1.length).forEach(child => {
        mount(child, el)
    })
}

就是这样。我们发现了节点之间的每个差异,并相应地修正了DOM。这个解决方案没有实现的是属性的修补。这会使博客文章更长,而且会错过要点。

Rendering a virtual tree in the real DOM

我们的虚拟DOM引擎已经准备好了。为了演示它,我们可以创建一些节点并渲染它们。让我们假设我们想要以下HTML结构:


    

Hello World ????

    

Thanks for reading the marc.dev blog ????

1) Create the virtual node with h

const node1 = h('div', { class: 'container' }, [
    h('div', null, 'X'),
    h('span', null, 'hello'),
    h('span', null, 'world'),
])

2) Mount the node to the DOM

我们想挂载新创建的DOM。在哪里?到文件最顶部的#app-div:

mount(node1, document.getElementById('app'))

结果应该是这样的:

从头创建您自己的vuei .js——第3部分(构建VDOM)_第1张图片

3) Create a second virtual node

现在,我们可以创建第二个节点,并对其进行一些更改。让我们添加一些节点,这样结果将是这样的:


    

Hello Dev ????

    

Thanks for reading the marc.dev blog

    

下面是创建该节点的代码:

const node2 = h('div', { class: 'container' }, [
    h('h1', null, 'Hello Dev ????'),
    h('p', null, [
        h('span', null, 'Thanks for reading the '),
        h('a', { href: 'https://marc.dev' }, 'marc.dev'),
        h('span', null, ' blog'),
    ]),
    h(
        'img',
        {
            src: 'https://media.giphy.com/media/26gsjCZpPolPr3sBy/giphy.gif',
            style: 'width: 350px; border-radius: 0.5rem;',
        },
        [],
    ),
])

如您所见,我们添加了一些节点,还更改了一个节点。

4) Render the second node

我们希望用第二个节点替换第一个节点,因此不使用mount。我们要做的是找出两者之间的区别,进行更改,然后渲染。所以我们修补它:

setTimeout(() => {
    patch(node1, node2)
}, 3000)

我在这里添加了一个超时,以便您可以看到代码DOM的变化。如果没有,就只能看到呈现的新VDOM。

Summary

就是这样!我们有一个非常基本的DOM引擎版本,它让我们:

创建虚拟节点

将虚拟节点挂载到DOM

从DOM中删除虚拟节点

找出两个虚拟节点之间的差异,并相应地更新DOM

你可以在我为你准备的Github要点中找到我们在这篇文章中做的代码。如果你只是想玩玩它,我还创建了一个Codepen,你可以这样做。

你可能感兴趣的:(javascript,js,dom,css,vue)