FrankKai / FrankKai.github.io

FE blog
https://frankkai.github.io/
363 stars 39 forks source link

一次简单的Virtual DOM实验 #88

Open FrankKai opened 6 years ago

FrankKai commented 6 years ago

这次实验基于Matt-Esch提供的实现了Virtual DOM算法的virtual dom 模型库,主要目的在于对Virtual DOM有一个粗略的认识。 基础认识:

主要内容:

FrankKai commented 6 years ago

动机

手动的DOM操作是很麻烦的,如果在对之前的DOM状态进行追踪就更是难上加难。解决这个问题的一个办法:写代码,就像无论什么时候状态发生变化,重新创建整个DOM一样。当然,如果每次应用状态更新的时候,你真正的重新创建了整个DOM,你的app将会非常非常慢,而且你的当前输入框将会失去焦点。

virtual-dom是一个模块集,这个模块集声明式代表应用中的DOM节点。所以与在应用状态更新时更新DOM不同,你可以简单地创建一个虚拟树或者VTree,这个树可以有和你预期一样的DOM状态。virtual-dom会在不重复创建DOM节点的情况下,明确指出如何去构建DOM树。

virtual-dom允许你在状态发生变化时升级view,它升级view的方式是创建一个完整的view的VTree,然后按照你描述的为DOM打补丁。这样将保持手动的DOM操作和之前您的应用程序代码的状态跟踪,为Web应用程序提升干净和可维护的呈现逻辑。

FrankKai commented 6 years ago
var h = require('virtual-dom/h');
var diff = require('virtual-dom/diff');
var patch = require('virtual-dom/patch');
var createElement = require('virtual-dom/create-element');

// 1:创建一个函数,它用来声明DOM的外观
function render(count)  {
    return h('div', {
        style: {
            textAlign: 'center',
            lineHeight: (100 + count) + 'px',
            border: '1px solid red',
            width: (100 + count) + 'px',
            height: (100 + count) + 'px'
        }
    }, [String(count)]);
}

// 2: 初始化文档
var count = 0;      // 我们需要一些app数据,这里我们只存一个count值

var tree = render(count);               // 我们需要一个初始化树
var rootNode = createElement(tree);     // 创建一个初始化的根DOM节点,并且将树绑定上去
document.body.appendChild(rootNode);    // 将这个绑定了树的DOM根节点,添加到当前的文档中

// 3: 升级DOM树的逻辑
setInterval(function () {
      count++;

      var newTree = render(count);
      var patches = diff(tree, newTree);
      rootNode = patch(rootNode, patches);
      tree = newTree; //这里是为了做下一次DOM更新
}, 1000);
FrankKai commented 6 years ago

DOM model

virtual-dom 暴露了一个代表DOM节点的对象集。"Document Object Model Model"也许看起来有些奇怪,但是它就是这么回事。它的意思是:原生的Javascript 树状结构,代表了原生的DOM节点树。这也就是VTree。

我们可以使用一种冗余的方式去直接使用对象创建VTree,或者我们可以使用更多的简洁的virtual-hyperscript。

例子-使用对象直接创建VTree

var VNode = require('virtual-dom/vnode/vnode');
var VText = require('virtual-dom/vnode/vtext');

function render(data) {
    return new VNode('div', {
        className: "greeting"
    }, [
        new VText("Hello " + String(data.name))
    ]);
}

module.exports = render;

例子-使用virtual-hyperscript创建VTree

var h = require('virtual-dom/h');
function render(data) {
    return h('.greeting', ['Hello ' + data.name]);
}
module.exports = render;

DOM模型的设计初衷是为了高效创建和读取表单。我们不创建真是的DOM树的原因在于,创建DOM节点并且读取节点的属性十一个非常昂贵的操作,我们应该避免这样做。读取一些DOM节点属性甚至可能导致一些副作用,所以基于真实的DOM节点去重新创建整个DOM数据结构,对于高性能的渲染是不合适的,而且也不容易推理。

VTree的设计初衷,是为了保证数据结构不变性。当它实际上可变时,你可以重写在多个地方重用节点,重用我们暴露出去的接收VTree作为参数但是不会改变树的函数。我们可以冻结模型中的对象,但是不能提高效率。

FrankKai commented 6 years ago

Element creation

createElement(tree:VTree) -> DOMNode

鉴于我们已经创建了VTree,因此我们需要一些方式去将这个虚拟树转换成一个真实的DOM树。这个函数是由create-element.js。第一次渲染的时候,我们将传递完整的VTree给create-element函数去创建等效的DOM节点。

FrankKai commented 6 years ago

Diff computation

diff(previous:VTree, current:VTree) -> PatchObject

虚拟DOM的主要动机在于:允许我们写出与之前状态完全独立的代码。所以当我们的应用树状态改变的时候,我们将生成一个新的VTree。diff函数创建了一个DOM补丁集合,这个集合基于当前的VTree和之前的VTree,并且更新之前的VTree去匹配当前的VTree。

FrankKai commented 6 years ago

Patch operations

patch(rootNode:DOMNode, patches:PatchObject) -> DOMNode newRootNode

一旦我们计算出补丁集并且应用到DOM上,我们需要一个函数去应用这些补丁。这由patch函数去做。有一个DOM根节点和一个DOM补丁集,patch函数将更新DOM。应用补丁到DOM后,DOM就和VTree表现一致了。