Mithril 虚拟 DOM 节点

虚拟 DOM 节点

基础

虚拟 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 类型

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 对象,以确保较高的渲染性能。

避免反面模式(anti-patterns)

避免存储可变 vnode

vnode 应该表示 DOM 在某个时间点的状态。Mithril 的渲染引擎会假定 vnode 是不变的,所以修改一个已经渲染了的 vnode 时,会导致意想不到的后果。

尽可能重用 vnode 来避免 diff,最好使用 onbeforeupdate 钩子来使其他开发者能明白你的意图。