这是我参与8月更文挑战的第8天,活动详情查看: 8月更文挑战
序言
Vue 的三大核心,响应式,VDOM,模板编译,我们之前有研究过响应式了,接下来就是研读 VDOM 了,于是我决定从 VDOM 的启发 库 ,snabbdom 开始学习,今天我们就来看看 snabbdom 给了我们带来什么样的启发。
安装snabbdom
- github 上 clone : 这里是TS版本的。
$ git clone https://github.com/snabbdom/snabbdom
- 使用 npm :这里是 build 出来的 JS 版的。
npm i -D snabbdom
snabbdom 简介
snabbdom 见名知意,虚拟 DOM。
- 那么你知道什么是 Virtual DOM 吗。通俗的说,Virtual DOM 就是一个 JS 对象,它是真实 DOM 的抽象,只保留一些有用的信息,更轻量地描述 DOM 树的结构。
- snabbdom 就是一个将真实 DOM 用特定格式的 JS 对象 描述,再将那个特定格式的 JS 对象转换成真实 DOM 的实现。
- 那种特定格式的 JS 对象叫做 VNode。
Node与 VNode
真实的 Node 对象。
<a href="http://www.baidu.com">去百度</a>
真实 Node 对应的的虚拟 Node (VNode )。
{"sel":"a",
"data":{
props:{
href:'http://www.baidu.com'
}
},
"text":"去百度",
children:"undefined",
elm:"undefined",
key:"undefined"
}
我们发现,真实 Node 上的属性在虚拟 Node 中都有相应的描述,信息是不会丢失的,就是换了一种表示法 。 VNode 通过 patch 函数可以变成真实 Node 组合成 DOM 挂载到页面上
Vnode 定义
export interface VNode {
sel: string | undefined;//标签名
data: VNodeData | undefined;//标签内的属性数据
children: Array<VNode | string> | undefined;//子标签
elm: Node | undefined;//当前节点对应的真实DOM节点
text: string | undefined;//当前节点文本
key: Key | undefined;// 子节点key属性
}
export interface VNodeData {
props?: Props;
attrs?: Attrs;
class?: Classes;
style?: VNodeStyle;
dataset?: Dataset;
on?: On;
hero?: Hero;
attachData?: AttachData;
hook?: Hooks;
key?: Key;
ns?: string; // for SVGs
fn?: () => VNode; // for thunks
args?: Array<any>; // for thunks
[key: string]: any; // for any other 3rd party module
}
我们来看一个简单的案例,这个案例我们将展示将一个 Vnode 对象转换成真实 Node 并且显示在页面上
import { h } from 'snabbdom/src/package/h'
import { init } from 'snabbdom/src/package/init'
// 创建patch函数
// 参数:数组,传入模块
const patch = init([])
// 创建虚拟节点
// 第一个参数:标签 + 选择器
// 第二个参数:如果是字符串就是标签的内容
let vnode = h('div#container.cls', 'Hello World')
const app = document.getElementById('app')
//让虚拟节点上DOM树
// 第一个参数:可以是 DOM 元素,内部会把 DOM 元素转换成 vnode
// 第二个参数: VNode
// 返回值:VNode
const oldVNode = patch(app, vnode)
我们可以看到在页面上展示一个内容需要三个步骤
- 使用 h 函数创建虚拟节点(VNode)。
- 定义要挂载节点的位子。
- 使用 patch 函数,使 VNode 变成真实的 Node 并且挂载到传入的节点位子。
小结:由上述案例,我们不禁疑问,h 函数如何创造 Vnode 的呢?patch 方法又是如何将 Vnode 转换成真实 Node并且挂载到页面中呢。
解密 h 函数
看一个函数,首先看入参和出参。 h 函数接受三个参数,
- 一个代表标签名的 sel 字符串,
- 第二个参数是模块数据的定义(也就是 class,click 等等)
- 第三个参数是子节点的形式
export function h(sel: string): VNode
export function h(sel: string, data: VNodeData | null): VNode
export function h(sel: string, children: VNodeChildren): VNode
export function h(sel: string, data: VNodeData | null, children: VNodeChildren): VNode
export function h (sel: any, b?: any, c?: any): VNode
我们又发现 h 函数通过函数重载来处理不同的参数传入。
if (c !== undefined) {
if (b !== null) {
data = b
}
if (is.array(c)) {
children = c
} else if (is.primitive(c)) {
text = c
} else if (c && c.sel) {
children = [c]
}
} else if (b !== undefined && b !== null) {
if (is.array(b)) {
children = b
} else if (is.primitive(b)) {
text = b
} else if (b && b.sel) {
children = [b]
} else { data = b }
}
if (children !== undefined) {
for (i = 0; i < children.length; ++i) {
if (is.primitive(children[i])) children[i] = vnode(undefined, undefined, undefined, children[i], undefined)
}
}
经过处理完后,可以分离出VNode 函数需要的参数。
export function vnode (sel: string | undefined,
data: any | undefined,
children: Array<VNode | string> | undefined,
text: string | undefined,
elm: Element | Text | undefined): VNode {
const key = data === undefined ? undefined : data.key
return { sel, data, children, text, elm, key }
}
我们可以看到 vnode 方法接受 4 个参数,返回成一个 VNode 对象。 小结:h 函数把接受的参数通过函数重载,将数据分离,然后给VNode函数,最后转换成相应格式的 VNode对象。 我们来看一个真实案例
let vnode = h(
"div#container.cls",
{
class: {
active: true,
},
style: {
background: "#fff",
},
on: {
click: clickFn,
},
dataset: {
name: "coolFish",
},
hook: {
init: function () {
console.log("init");
},
create: function () {
console.log("create");
},
insert: function () {
console.log("insert");
},
prepatch: function () {
console.log("beforePatch");
},
update: function () {
console.log("update");
},
postpatch: function () {
console.log("postPatch");
},
destroy: function () {
console.log("destroy");
},
remove: function (ch, rm) {
console.log("remove");
rm();
},
},
},
[h("p", {}, "我是第一个Vnode")]
);
function clickFn() {
console.log("click");
}
以上 h 函数可以生成以下 VNode 我们可以看到他的父容器是一个 id 为 container 的 div。他有class,click事件,style, 为何要转换成虚拟DOM,因为精细化比较,是使用新虚拟DOM和老虚拟DOM进行比较,算出如何最小量更新,然后反应到真实DOM上。 有了VNode,通过 patch函数就能将该VNode挂载到页面中。