一个指令定义对象可以提供如下几个钩子函数 (均为可选):
bind
、inserted
、 update
、componentUpdated
、 unbind
具体的说明请参考vue官网的 自定义指令说明文档。
首先是解析vue实例的directives
选项
/**
* 将原始函数指令规范化为对象格式。
*/
function normalizeDirectives (options: Object) {
const dirs = options.directives
if (dirs) {
for (const key in dirs) {
const def = dirs[key]
// 如果指令的key值是一个函数的话,那就是'bind'和'update'公用一个处理函数
if (typeof def === 'function') {
dirs[key] = { bind: def, update: def }
}
}
}
}
我们打开文件src/core/vdom/modules/directives.js
。
首先vue指令的声明分
全局定义
和局部定义
1、全局定义:Vue.directive
2、局部定义:directives: {}
3、指令不能含有大写字母
/* @flow */
import { emptyNode } from 'core/vdom/patch'
import { resolveAsset, handleError } from 'core/util/index'
import { mergeVNodeHook } from 'core/vdom/helpers/index'
export default {
create: updateDirectives,
update: updateDirectives,
destroy: function unbindDirectives (vnode: VNodeWithData) {
updateDirectives(vnode, emptyNode)
}
}
// 更新指令,只要新的虚拟节点或者旧的虚拟节点存在一个,就会调用_update
function updateDirectives (oldVnode: VNodeWithData, vnode: VNodeWithData) {
if (oldVnode.data.directives || vnode.data.directives) {
_update(oldVnode, vnode)
}
}
// 新建和更新都是在这个函数里面
function _update (oldVnode, vnode) {
const isCreate = oldVnode === emptyNode
const isDestroy = vnode === emptyNode
// 新的指令
const oldDirs = normalizeDirectives(oldVnode.data.directives, oldVnode.context)
// 旧的指令
const newDirs = normalizeDirectives(vnode.data.directives, vnode.context)
const dirsWithInsert = []
const dirsWithPostpatch = []
let key, oldDir, dir
for (key in newDirs) {
oldDir = oldDirs[key]
dir = newDirs[key]
// 如果旧的指令不存在就调用'bind'钩子函数,否则就调用'update'钩子函数。
// bind:只调用一次,指令第一次绑定到元素时调用。
if (!oldDir) {
// 调用'bind'钩子函数
callHook(dir, 'bind', vnode, oldVnode)
if (dir.def && dir.def.inserted) {
dirsWithInsert.push(dir)
}
} else {
// existing directive, update
dir.oldValue = oldDir.value
dir.oldArg = oldDir.arg
// 数据更新 =》虚拟节点更新=》指令的更新=》调用'update'钩子函数
/*
update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改
变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见
下)。
*/
callHook(dir, 'update', vnode, oldVnode)
if (dir.def && dir.def.componentUpdated) {
dirsWithPostpatch.push(dir)
}
}
}
// 如果提供了'inserted'钩子函数,就是调用'inserted'钩子
if (dirsWithInsert.length) {
const callInsert = () => {
for (let i = 0; i < dirsWithInsert.length; i++) {
callHook(dirsWithInsert[i], 'inserted', vnode, oldVnode)
}
}
// 创建阶段,调用‘inserted’钩子
if (isCreate) {
mergeVNodeHook(vnode, 'insert', callInsert)
} else {
callInsert()
}
}
// 指令所在组件的 VNode 及其子 VNode 全部更新后调用。
if (dirsWithPostpatch.length) {
mergeVNodeHook(vnode, 'postpatch', () => {
for (let i = 0; i < dirsWithPostpatch.length; i++) {
callHook(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode)
}
})
}
// unbind:只调用一次,指令与元素解绑时调用。
if (!isCreate) {
for (key in oldDirs) {
if (!newDirs[key]) {
// no longer present, unbind
callHook(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy)
}
}
}
}
指令的解析是从createElm
创建节点函数来调用的,我们可以看看整个调用序列图:
const isCreate = oldVnode === emptyNode
const isDestroy = vnode === emptyNode
可以看到上面定义了两个变量,isCreate
、isDestroy
,通过字面意思我们就可以知道他的用途了。
1、旧的虚拟节点oldVnode是一个空的节点就是新增 (isCreate )
2、新的虚拟节点vnode是一个空的节点就是销毁 (isDestroy )
网友评论