Snabbdom 简介

h 函数:用 JS 对象描述页面

掌握 h 函数的常用写法、选择器语法和虚拟节点的内部结构,能用嵌套子节点描述任意页面。

🎯 引言

上一篇我们用 h('div#container', 'Hello Snabbdom') 创建了第一个虚拟节点。这篇我们把 h 函数彻底讲透:它的常用写法、选择器语法,以及创建出来的虚拟节点到底长什么样。学完你就能用纯 JS 对象描述任意页面结构。


🧱 h 函数是什么

h 函数的作用只有一个:创建虚拟节点(VNode)。它接收你对一个元素的描述,返回一个普通的 JS 对象,我们称之为虚拟节点

main.js
import { h } from 'snabbdom';

const vnode = h('div', '我是文字');
console.log(vnode);

打印出来的对象大致是:

{
    sel: 'div',
    data: {},
    text: '我是文字',
    children: undefined,
    elm: undefined,
    key: undefined
}

各字段的含义:

字段含义
sel选择器,描述标签名、idclass
data数据对象,放样式、属性、事件等(下一篇详解)
text文本内容
children子节点数组
elm对应的真实 DOM 元素(渲染后由 Snabbdom 填入)
key节点的唯一标识(第五篇详解)

重点记住 elm 字段:虚拟节点和真实 DOM 是一一对应的,渲染之后 Snabbdom 会把真实元素挂到 elm 上,后续更新全靠它找到目标。


🧱 三种常用写法

h 函数很灵活,最常用的有三种形态:

main.js
// 1. 只有选择器:一个空元素
h('div');

// 2. 选择器 + 文本:h(sel, text)
h('p', '一段文字');

// 3. 选择器 + 子节点数组:h(sel, [children])
h('ul', [
    h('li', '苹果'),
    h('li', '香蕉')
]);

还有第四种形态 h(sel, data, text/children),中间的 data 用来传样式、事件等数据,我们留到下一篇展开。

只有一个子节点时,数组可以省略:h('div', h('span', '唯一子节点'))。数字也会被自动转成字符串文本:h('p', 100) 渲染出的就是 100

🧱 选择器语法

h 的第一个参数叫 sel(选择器),写法借鉴了 CSS 选择器,但只支持标签名、#id.class 三种:

main.js
h('div'); // <div></div>
h('div#app'); // <div id="app"></div>
h('div.box'); // <div class="box"></div>
h('div#app.box.active'); // <div id="app" class="box active"></div>
h('span'); // <span></span>

一条使用经验:id 和 class 写在选择器里,其余属性(href、placeholder 等)交给 data,不要硬塞进选择器。

选择器不支持属性选择器(如 input[type=text])和层级选择器(如 div p)。嵌套结构请用 children 数组表达。

💡 嵌套:描述真实页面

真实页面都是层层嵌套的,用 children 数组一层层描述即可。比如这样一个结构:

<div id="box">
    <h1 class="title">水果列表</h1>
    <ul>
        <li>苹果</li>
        <li>香蕉</li>
        <li>橙子</li>
    </ul>
</div>

对应的虚拟 DOM 写法:

main.js
const vnode = h('div#box', [
    h('h1.title', '水果列表'),
    h('ul', [
        h('li', '苹果'),
        h('li', '香蕉'),
        h('li', '橙子')
    ])
]);

渲染出来和手写 HTML 一模一样。是不是有点眼熟?Vue 模板编译之后,本质上也是生成类似这样的虚拟节点树


🧾 小节总结

  • h 函数用于创建虚拟节点,返回包含 seldatatextchildrenelmkey 的对象。
  • 常用写法:h(sel)h(sel, text)h(sel, [children]);数据通过 h(sel, data, ...) 传入。
  • 选择器只支持标签名、#id.class,其余属性放到 data 里。
  • 单个子节点可省略数组,数字会被自动转成文本。
  • 虚拟节点渲染后通过 elm 字段与真实 DOM 一一对应。

❓ 知识问答

Q1:text 和 children 能同时用吗?

A:不能。一个虚拟节点要么是文本节点(有 text),要么有子节点数组(children),二者互斥。更新时 Snabbdom 也是按这个规则处理的,第六篇 Diff 会详细讲。

Q2:h('div') 不传任何内容,会发生什么?

A:会渲染出一个空的 <div></div>。没有 text 也没有 children,就是一个空元素。

Q3:选择器里能写多个 id 吗?

A:语法上不会报错,但 HTML 规范中一个元素只能有一个 id,写多个没有意义,实际开发中也不会这么用。

Q4:同一个虚拟节点对象能渲染多次吗?

A:不建议。虚拟节点渲染后会挂上 elm 指向真实元素,复用同一个对象会导致指向混乱。需要重复结构时,写一个函数,每次调用 h 创建新对象。


🧪 小练习

h 函数描述下面的结构,并渲染到页面上:

<div id="card">
    <h2 class="title">个人名片</h2>
    <p class="intro">一名前端初学者</p>
</div>
main.js
import { init, h } from 'snabbdom';

const patch = init([]);

// 请在这里编写代码

🎉 恭喜你已经掌握了 h 函数!下一篇我们学习模块机制,给虚拟节点加上样式、属性和事件。