⚡ 30 秒速记

  • h 把便于书写的输入整理成虚拟节点
  • 标签或组件决定节点种类
  • 文本、数组和插槽分别规范化
  • 不自动替业务推导稳定身份
  • 生成描述与真正挂载分开

h 像虚拟节点的构造助手,让开发者不必手写每个内部字段。 它接收元素或组件、属性以及子内容,再把文本、列表和插槽整理成渲染器需要的形式。比如 h('div', { class: 'box' }, '你好') 描述一个带文字的元素,但不会立刻修改页面。列表身份仍由业务提供稳定键,位置编号无法自动代表数据身份。

版本校准: 本文出现 Object.defineProperty、Dep、Watcher 和双端 Diff 时,主要描述 Vue 2 实现;Vue 3 使用 Proxy、effect 与新的渲染器路径。Vue 2 已于 2023-12-31 结束维护,新项目应以 Vue 3 为基线,旧项目参考 Vue 2 EOL 官方说明 制定迁移与安全策略。

自从有了 VNode ,开发页面的方式就变成了书写 VNode,但如果日常开发中需要手写 VNode ,那绝对是反人类的,在“组件的本质”一章中我们使用了 snabbdom 的 h 函数来辅助讲解一些小例子,h 函数作为创建 VNode 对象的函数封装,在一定程度上改善了这个问题,但却没有解决本质问题,这也是为什么我们需要模板或 jsx 的原因。但 h 函数依然很重要,因为无论是模板还是 jsx 都需要经过编译,那么是直接编译成 VNode 树好呢?还是编译成由 h 函数组成的调用集合好呢?这个其实很难说,但可以肯定的一点是,我们将可公用、灵活、复杂的逻辑封装成函数,并交给运行时,这能够大大降低编译器的书写难度,甚至经过编译器生成的代码也具有一定的可读性,而 h 函数就是众多运行时函数中的一个。

# 在VNode创建时确定其类型 - flags

一个最简单的 h 函数如下:

function h() {
  return {
    _isVNode: true,
    flags: VNodeFlags.ELEMENT_HTML,
    tag: 'h1',
    data: null,
    children: null,
    childFlags: ChildrenFlags.NO_CHILDREN,
    el: null
  }
}

这个 h 函数只能用来创建一个空的 <h1></h1> 标签,可以说没有任何意义。为了让 h 函数更加灵活,我们可以增加一些参数,问题是哪些内容应该提取到参数中呢?如果提取的参数多了,就会导致函数的使用不便,如果提取的参数少了又会导致函数的功能不足,所以这也是一个探索的过程。实际上只需要把 tag、data 和 children 提取为参数即可:

function h(tag, data = null, children = null) {
  //...
}

我们来看看为什么三个参数就能满足需求,对于 _isVNode 属性,它的值始终都为 true,所以不需要提取到参数中。对于 flags 属性,我们可以通过检查 tag 属性值的特征来确定该 VNode 的 flags 属性值,如下:

webapp
公众号
开发者导航
切换夜间模式
点击侧边栏上一篇
点击侧边栏下一篇
折叠侧边栏
收起全部