# 功能模块的钩子函数

我们在分析patch函数时,无论是元素类型的 VNode 还是组件占位 VNode,都只是分析了 DOM 节点及其子节点是如何修补的,但是没有提到 DOM 元素节点上的class/style/events等数据时如何修补的。实际上,在patchVnode的过程中,会执行很多功能模块的钩子函数,比如:

  • create钩子:DOM 元素节点创建时/初始化组件时调用
  • activate钩子:组件激活时调用
  • update钩子:DOM 节点更新时调用
  • remove钩子:DOM 节点移除时调用
  • destroy钩子:组件销毁时调用

而这些功能模块有:

  • Web 平台相关模块
    • attrs模块:处理节点上的特性attribute
    • klass模块:处理节点上的类class
    • events模块:处理节点上的原生事件
    • domProps模块:处理节点上的属性property
    • style模块:处理节点上的内联样式style特性
    • transition模块
  • 核心模块
    • ref模块:处理节点的引用ref
    • directives模块:处理节点上的指令directives

每一功能模块包含了若干个钩子,但基本都存在create钩子和update钩子,用于在 DOM 节点创建、更新以及销毁时,如何来处理这些挂载上节点之上的数据。

# 钩子函数的来源

调用createPatchFunction函数时,会将所有的功能模块当做参数传入,以返回专属于 Web 平台的patch函数。

// src/platforms/web/runtime/patch.js
import * as nodeOps from 'web/runtime/node-ops'
import { createPatchFunction } from 'core/vdom/patch'
import baseModules from 'core/vdom/modules/index'
import platformModules from 'web/runtime/modules/index'

// the directive module should be applied last, after all
// built-in modules have been applied.
const modules = platformModules.concat(baseModules)

export const patch: Function = createPatchFunction({ nodeOps, modules })
1
2
3
4
5
6
7
8
9
10
11

platformModules专属模块的来源:

// src/platforms/web/runtime/modules/index.js
import attrs from './attrs'
import klass from './class'
import events from './events'
import domProps from './dom-props'
import style from './style'
import transition from './transition'

export default [
  attrs,
  klass,
  events,
  domProps,
  style,
  transition
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

baseModules的来源:

// src/core/vdom/modules/index.js
import directives from './directives'
import ref from './ref'

export default [
  ref,
  directives
]
1
2
3
4
5
6
7
8

这些引入的各个模块暴露的对象,都包含了钩子的名称和处理函数,以class为例,模块的结构如下所示。

function updateClass(oldVnode, vnode) {
  // ...
}
export default {
  create: updateClass,
  update: updateClass
}
1
2
3
4
5
6
7

# 钩子函数的合并

createPatchFunction函数执行时,第一时间就是合并这些钩子函数,将所有功能模块同类型的钩子函数合并到数组里,

// src/core/patch.js
const hooks = ['create', 'activate', 'update', 'remove', 'destroy']
export function createPatchFunction (backend) {
  let i, j
  const cbs = {}

  const { modules, nodeOps } = backend

  for (i = 0; i < hooks.length; ++i) {
    cbs[hooks[i]] = []
    for (j = 0; j < modules.length; ++j) {
      if (isDef(modules[j][hooks[i]])) {
        cbs[hooks[i]].push(modules[j][hooks[i]])
      }
    }
  }
  // ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

经过合并以后,cbs变成了如下的结构,这些钩子将在不同的时机被一一调用。

cbs = {
  create: [
    attrs.create,
    klass.create,
    events.create,
    domProps.create,
    style.create,
    transition.create,
    ref.create,
    directives.create
  ],
  update: [
    attrs.update,
    klass.update,
    events.update,
    domProps.update,
    style.update,
    ref.update,
    directives.update
  ],
  activate: [
    transition.activate
  ],
  remove: [
    transition.remove
  ],
  destroy: [
    ref.destroy,
    directives.destroy
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 钩子函数的调用时机

# create 钩子

DOM 元素创建好后以及初始化创建好的子组件时会调用invokeCreateHooks函数,而create钩子的调用主要就是封装在invokeCreateHooks函数里。

// src/core/patch.js
  function invokeCreateHooks (vnode, insertedVnodeQueue) {
    for (let i = 0; i < cbs.create.length; ++i) {
      cbs.create[i](emptyNode, vnode)
    }
    i = vnode.data.hook // Reuse variable
    if (isDef(i)) {
      if (isDef(i.create)) i.create(emptyNode, vnode)
      if (isDef(i.insert)) insertedVnodeQueue.push(vnode)
    }
  }
1
2
3
4
5
6
7
8
9
10
11

提示

还有几处零散地调用create钩子和invokeCreateHooks函数的地方,在此不再详述。

# update 钩子

在修补新旧 VNode 时会调用update钩子。

// src/core/patch.js
  function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
    // ...
    if (isDef(data) && isPatchable(vnode)) {
      // 调用各个模块的 update 钩子
      for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
      // 调用(带有自定义指令且指令存在 update 钩子的元素类型的) VNode 的 update 钩子
      if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
    }
    // ...
  }
1
2
3
4
5
6
7
8
9
10
11

# 其他说明

# 钩子函数参数

调用这些钩子函数时,第一个参数是旧 VNode,第二个参数是新 VNode

# DOM 操作的节点

有些模块的操作主体是 DOM 元素节点,比如class模块、attrs模块。

对于元素类型的 VNode,所操作的是其对应的 DOM 元素节点。

对于组件占位 VNode,所操作的是组件视图 DOM 的根元素节点。

# 各模块详解

# events 模块

详见events 模块

# class 模块

详见class 模块

# style 模块

详见style 模块

# attrs 模块

详见attrs 模块

# dom-props 模块

详见dom-props 模块

# transition 模块

TODO: 待添加

# ref 模块

详见ref 模块

# directives 模块

TODO: 待添加

本站总访问量    次