虚拟 DOM 节点(vnode)是用于表示 DOM 元素(或 DOM 的一部分)的 JavaScript 对象。Mithril 的虚拟 DOM 引擎使用 vnode 树来生成 DOM 树。
vnode 通过 m() hyperscript 工具来创建:
m("div", {id: "test"}, "hello")
Hyperscript 也可以直接使用组件:
// 定义一个组件
var ExampleComponent = {
view: function(vnode) {
return m("div", vnode.attrs, ["Hello ", vnode.children])
}
}
// 使用该组件
m(ExampleComponent, {style: "color:red;"}, "world")
// 最终生成的 HTML 为:
// <div style="color:red;">Hello world</div>
虚拟 DOM 节点(vnode)是一个 JavaScript 对象,具有以下属性:
属性 | 类型 | 说明 |
---|---|---|
tag |
String|Object |
DOM 元素的 nodeName 。也可以是 [ 用来表示一个片段,# 用来表示文本节点,< 用来表示 HTML 字符串。另外,也可以是组件。 |
key |
String? |
用于把 DOM 元素映射到数据数组中对应项的值。 |
attrs |
Object? |
DOM 属性,事件,属性和生命周期方法的 hashmap。 |
children |
(Array|String|Number|Boolean)? |
在大多数 vnode 类型中,children 属性是一个 vnode 数组。对于文本和 HTML 字符串节点,children 属性是字符串、数字和布尔值。 |
text |
(String|Number|Boolean)? |
如果 vnode 只包含文本,则可以用该属性代替 children 。使用这个属性是出于性能的考虑。组件 vnode 始终不会使用 text 属性,即使组件只包含文本。 |
dom |
Element? |
指向与 vnode 对应的元素。此属性在 oninit 生命周期方法中为 undefined 。在节点片段和 HTML 节点中,dom 指向所有节点中的第一个元素。 |
domSize |
Number? |
只在节点片段和 HTML 节点中存在,在其他类型的 vnode 中为 undefined 。它表示 vnode 表示的 DOM 元素的数量(从 DOM 元素引用的元素开始)。 |
state |
Object |
在重绘时保持不变的对象。在组件 vnode 中,state 是组件对象的浅克隆。 |
events |
Object? |
一个保存事件处理操作的对象,在重绘时保持不变,可通过 DOM API 移除事件。如果没有已定义的事件,则 events 属性为 undefined 。这个属性只在 Mithril 内部使用,开发者不要使用它。 |
vnode 的 tag
属性决定了它的类型。有 5 种 vnode 类型:
vnode 类型 | 示例 | 说明 |
---|---|---|
元素 | {tag: "div"} |
表示 DOM 元素。 |
片段 | {tag: "[", children: []} |
表示 DOM 元素的列表,其父 DOM 元素可能还包含不在片段中的其他元素。当使用 m() 函数时,只能通过把数组传入 m() 函数的 children 参数中来创建 vnode 片段。m("[") 无法创建有效的 vnode。 |
文本 | {tag: "#", children: ""} |
表示 DOM 文本节点。 |
HTML 字符串 | {tag: "<", children: "<br>"} |
表示来自 HTML 字符串的 DOM 元素列表。 |
组件 | {tag: ExampleComponent} |
如果 tag 是包含 view 方法的 JavaScript 对象,vnode 则表示通过渲染该组件生成的 DOM。 |
Mithril 中所有的 vnode 都是通过 mithril/render/vnode
模块生成的。这确保了现代 JavaScript 引擎可以通过始终将 vnode 编译到同一隐藏类来优化虚拟 DOM diff 的性能。
当你在其他的库中需要使用 vnode 时,应该调用此模块,而不是直接写 JavaScript 对象,以确保较高的渲染性能。
vnode 应该表示 DOM 在某个时间点的状态。Mithril 的渲染引擎会假定 vnode 是不变的,所以修改一个已经渲染了的 vnode 时,会导致意想不到的后果。
尽可能重用 vnode 来避免 diff,最好使用 onbeforeupdate
钩子来使其他开发者能明白你的意图。