美文网首页
如何自己写简单的virtual dom(读博客笔记)

如何自己写简单的virtual dom(读博客笔记)

作者: 小王啊_ | 来源:发表于2017-07-24 14:40 被阅读0次

原文

正常的dom

<ul class=”list”>
  <li>item 1</li>
  <li>item 2</li>
</ul>

用js的object来代表dom

{
    type: 'ul', props: {'class': 'list'}, children: [
        {type: 'li', props: {}, children: ['item 1']},
        {type: 'li', props: {}, children: ['item 2']}
    ]
}

写个帮助方法创建js的dom

function helper(type, props, ...children) {
    return {type, props, children};
}

现在就可以这样写:

helper('ul', {'class': 'list'}, 
    helper('li', {}, 'item 1'),
    helper('li', {}, 'item 2')
)

可以通过babel 来转换jsx

实现从我们的js的object到真实dom

function createElement(node) {
    if (typeof node == 'string') {
        return document.createTextNode(node);
    }
    $el = document.createElement(node.type);
    node.children
        .map(createElement)
        .forEach($el.appendChild.bind($el));
    return $el; 
}

接下来处理diff

有四种情况

  • 新增
// old
<ul>
    <li>item 1</li>
</ul>
// new 
<ul>
    <li>item 1</li>
    <li>item 2</li>
</ul>
  • 删除
// old
<ul>
    <li>item 1</li>
    <li>item 2</li>
</ul>
// new 
<ul>
    <li>item 1</li>
</ul>
  • 替换
// old
<div>
    <p>item 1</p>
    <button>cpck it</button>
</div>
// new 
<div>
    <p>item 1</p>
    <p>hello</p>
</div>
  • 节点一致,子节点不一致
// old
<ul>
    <li>item 1</li>
    <li>
        <span>hello</span>
        <div>hi!</div>
    </li>
</ul>
// new 
<ul>
    <li>item 1</li>
    <li>
        <span>hello</span>
        <span>hi!</span>
    </li>
</ul>

所以我们可以写一个更新函数,接收三个参数,$parent、newNode、oldNode, 其中$parent是真实dom元素,并且是虚拟节点的父节点。(暂时不考虑props)

当无新节点或者旧节点时

function updateElement($parent, newNode, oldNode,  index = 0) {
    // 无旧节点
    if (!oldNode) {
        $parent.appendChild(newNode);
    // 无新节点
    } else if (!newNode) {
        $parent.removeChild(
            $parent.childNodes[index];
        );
    }
}

有新节点和旧节点时,需要判断节点是否改变,所以我们可以先写一个判断节点是否改变的函数。

function changed(node1, node2) {
            // 基础数据类型判断
    return typeof node1 !== typeof node2 ||
            // 文本节点时是否一致
           typeof node1 == 'string' && node1 !== node2 ||
           // 元素节点时类型是否一致
           node1.type !== node2.type;
}

那么现在我们就可以完善一下 updateElement 函数:

function updateElement($parent, newNode, oldNode,  index = 0) {
    // 无旧节点
    if (!oldNode) {
        $parent.appendChild(newNode);
    // 无新节点
    } else if (!newNode) {
        $parent.removeChild(
            $parent.childNodes[index];
        );
        // 新旧节点发生变化时
    } else if (changed(newNode, oldNode)) {
        $parent.replaceChild(
            createElement(newNode), 
            $parent.childNodes[index];
        )
    }
}

最后不过也非常重要的事情

我们在对比节点时,需要保证它们的子节点也需要对比,才能判断他们的差异。在写代码之前我们需要考虑以下几个问题:

  1. 我们只需要对比元素节点而不用对比文本节点(文本节点无子节点);
  2. 我们把现在这个节点当做父节点;
  3. 我们需要一个一个节点对比,甚至是undefined,我们函数中需要有能应对undefined这种情况的能力;
  4. index只是子节点的索引。

考虑到以上,我们可以继续完善 updateElement 函数:

function updateElement($parent, newNode, oldNode,  index = 0) {
    // 无旧节点
    if (!oldNode) {
        $parent.appendChild(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 len = newNode.children.length > oldNode.children.length ? newNode.children.length : oldNode.Children.length;
        for (var i = 0; i<len; i++) {
            updateElement(
                $parent.childNodes[index],
                newNode.childNodes[i],
                oldNode.childNodes[i],
                i
            );
        }
    }
}

现在我们从整体来看

// index.html
<button id="reload">RELOAD</button>
<div id="root"></div>

js(babel+jsx)

function createElement(node) {
    if (typeof node == 'string') {
        return document.createTextNode(node);
    }
    $el = document.createElement(node.type);
    node.children
        .map(createElement)
        .forEach($el.appendChild.bind($el));
    return $el; 
}

function changed(node1, node2) {
            // 基础数据类型判断
    return typeof node1 !== typeof node2 ||
            // 文本节点时是否一致
           typeof node1 == 'string' && node1 !== node2 ||
           // 元素节点时类型是否一致
           node1.type !== node2.type;
}

function updateElement($parent, newNode, oldNode,  index = 0) {
    // 无旧节点
    if (!oldNode) {
        $parent.appendChild(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 len = newNode.children.length > oldNode.children.length ? newNode.children.length : oldNode.Children.length;
        for (var i = 0; i<len; i++) {
            updateElement(
                $parent.childNodes[index],
                newNode.childNodes[i],
                oldNode.childNodes[i],
                i
            );
        }
    }
}

const a = (
  <ul>
    <li>item 1</li>
    <li>item 2</li>
  </ul>
);

const b = (
  <ul>
    <li>item 1</li>
    <li>hello!</li>
  </ul>
);

const $root = document.getElementById('root');
const $reload = document.getElementById('reload');

updateElement($root, a);

$reload.addEventListener('click', () => {
    updateElement($root, a, b);
})

总结

我们到现在已经基本完成了 Virtual Dom 的简单实现,通过这我们应该能够了解 Virtual Dom 的基本原理,和了解 React 内部基本原理。

在这篇文章中我们还有一些我们没完成的东西,如下:

  • 设置节点的属性,并且计算差别和更新它们;
  • 节点的事件监听;
  • 让我们的 Virtual Dom 和组件工作,比如 React;
  • 拿到真实的Dom的引用;
  • 支持其它库直接操作真实DOM;
  • 其它...

相关文章

  • 如何自己写简单的virtual dom(读博客笔记)

    原文 正常的dom 用js的object来代表dom 写个帮助方法创建js的dom 现在就可以这样写: 可以通过b...

  • virtual dom的实现原理

    前言 react的virtual dom非常强大,本篇文章将会简单讨论virtual dom的实现思路~具体步骤如...

  • React进阶篇(三)diff算法

    如何计算Virtual Dom中真正变化的部分,这就需要diff算法。 Virtual Dom配合高效的diff算...

  • Virtual DOM

    一、Virtual DOM 是什么 本质上来说,Virtual DOM 只是一个简单的 JS 对象,并且最少包含 ...

  • virtual DOM 实现原理

    virtual DOM 实现: virtual-dom Snabbdom 对比 数据结构virtual Node ...

  • 简单-理解 Virtual DOM

    变化这件事谈论页面的变化之前,咱们先看下数据和页面(视觉层面的页面)的关系。数据是隐藏在页面底下,通过渲染展示给用...

  • Day7:virtual dom & MVVM

    virtual dom 什么是virtual dom 虚拟dom 用JS模拟DOM结构 DOM变化的对比,放在JS...

  • 实现简单render函数

    什么是Virtual Dom React和Vue2都使用了Virtual Dom技术,Virtual Dom并不是...

  • Virtual DOM

    什么是 Virtual DOM Virtual DOM(虚拟 DOM),是由普通的 JS 对象来描述 DOM 对象...

  • Javascript 简要,你一定要看看

    1.Virtual DOM Vue 和 React 都使用了 Virtual DOM,那么什么是 Virtual ...

网友评论

      本文标题:如何自己写简单的virtual dom(读博客笔记)

      本文链接:https://www.haomeiwen.com/subject/smjbkxtx.html