Snabbdom 源码解析 - createElm函数

452 阅读2分钟

「这是我参与2022首次更文挑战的第9天,活动详情查看:2022首次更文挑战

createElm函数

路径:core/vdom/patch.js

createElm函数的作用是把VNode节点转换成对应的DOM元素,把DOM元素存储在对象的 Element 属性中,但是并没有把创建的DOM元素挂载到对应的节点上,那么什么时候进行渲染呢?

这个函数内部可以分成三个过程

image.png

过程1

执行用户设置的 init 钩子函数

代码片段

// 执行用户设置的 init 钩子函数
    let i: any
    let data = vnode.data
    if (data !== undefined) {
      const init = data.hook?.init
      if (isDef(init)) {
        init(vnode)
        data = vnode.data
      }
    }

解析

  • vnode.data 这个就是我们使用h函数创建vnode的时候,传入的参数

    • 像这样:vnode = h('div#container.xxx', 'Hello WorldAAAAAA')
    • 或者这样:h('p', {on: {click: eventHandler }}, 'World') 关于h函数,大家可以看一下我之前写的 h函数的解析
  • data.hook?.init 这块也是我们用户进行传递的内容

    • hook?.init?ES 链式判断运算符 ?
    data.hook?.init
    //等价于
    data.hook && data.hook.init;
    
  • isDef(init) 这就是很简单,判断用户是否有定义 init 的内容

  • init(vnode) init 钩子函数是在创建真实DOM之前,让用户可以对vnode可以进行修改,比如可以更改样式或者其他属性

过程2

把 vnode 转换成真实 DOM 对象(没有渲染到页面上,而是把创建的对象挂载到 vnode的element属性上)

代码片断

const children = vnode.children
const sel = vnode.sel
if (sel === '!') {
      if (isUndef(vnode.text)) {
        vnode.text = ''
      }
      vnode.elm = api.createComment(vnode.text!)
} else if (sel !== undefined) {
  ...
  中间省略
} else {
  vnode.elm = api.createTextNode(vnode.text!)
}

解析

  • children变量就是 vnode的子节点
  • sel是选择器
  • sel === '!'这里的判断旧比较奇怪了
    • 首先我们先看一下我们在html中的注释格式 <!-- 1 -->
    • 这样就很明确了,如果sel === '!' 那么接下来就是注释节点的创建了
  • sel !== undefined会创建对应的DOM元素
  • else 中就很简单了:创建文本节点

过程3

过程3最简单,直接返回vnode.elem也就是返回新创建的 DOM

总结

  • 如果当前元素是注释节点,会调用 createComment 来创建一个注释节点,然后挂载到 vnode.elm

  • 如果不存在选择器,只是单纯的文本,调用 createTextNode 来创建文本,然后挂载到 vnode.elm

  • 如果存在选择器,会对这个选择器做解析,得到 tagidclass

    1. 调用 createElementcreateElementNS 来生成节点,并挂载到 vnode.elm

    2. 接着调用 module 上的 create hook,如果存在 children,遍历所有子节点并递归调用 createElm 创建 dom,通过 appendChild 挂载到当前的 elm 上,不存在 children 但存在 text,便使用 createTextNode 来创建文本。

    3. 最后调用调用元素上的 create hook 和保存存在 insert hookvnode,因为 insert hook 需要等 dom 真正挂载到 document 上才会调用,这里用个数组来保存可以避免真正需要调用时需要对 vnode 树做遍历。