1.自定义指令的执行时期
- 我们先了解一下vnode进行patch生成真实dom的生命周期:(图中说到的“修补”即patch)
- vdom生成真实dom的生命周期当中,create、update、destroy这三个生命周期,会去执行自定义指令:
export default {
create: updateDirectives,
update: updateDirectives,
destroy: (vnode) {
updateDirectives(vnode, emptyVnode)
}
}
function updateDirectives (oldVnode, vnode) {
if (oldVnode.data.directives || vnode.data.directives) {
_update(oldVnode, vnode);
}
}
2.指令执行逻辑:updateDirectives
- 在生成真实dom的三个生命周期当中,无论是create、update、destroy都是去执行updateDirectives
- updateDirectives的第一步操作就是去解析指令
- 当我们在一个节点当中使用了自定义指令,会在该节点的vnode中注入directives来存放指令相关信息,然后解析vnode中的directives:
function _update (oldVnode, newVnode) {
const isCreate = oldVnode === emptyVnode // 如果旧节点是空,则是创建阶段
const isDestroy = newVnode === emptyVnode // 新节点是空,则是销毁阶段
/**
解析vnode中的directives
至于怎么向vnode中注入directives,属于模版编译方面的知识了,本次不讲这些
**/
var oldDirs = normalizeDirectives$1(oldVnode.data.directives, oldVnode.context);
var newDirs = normalizeDirectives$1(newVnode.data.directives, newVnode.context);
}
- 例子:当我们在节点使用以下指令时,normalizeDirectives会帮我们解析成:
Vue.directive('focus', {
inserted: (el) => el.focus(),
bind: () => {/** ... **/},
unbind: () => {/** ... **/},
componentUpdated: () => {/** ... **/},
update: () => {/** ... **/},
})
// 节点使用 <div v-focus></div>
// normalizeDirectives 解析结果
{
v-focus: {
def: {
inserted: (el) => el.focus(),
bind: () => {/** ... **/},
unbind: () => {/** ... **/},
update: () => {/** ... **/},
componentUpdated: () => {/** ... **/},
}
modifiers: {},
name: "focus",
rawName: "v-focus"
}
}
- 解析结果当中的def对象里面的函数就是指令的周期函数,来看下这些周期函数的执行时机:
- vnode的create阶段:
1. 执行bind
2. 执行insert
- vnode的update阶段:
1. 执行update
2. 执行insert
3. 执行componentUpdated
- vnode的destroy阶段,
1. 执行unbind
\
- 我们修改_update方法下面如下:
function _update (oldVnode, newVnode) {
const isCreate = oldVnode === emptyVnode // 如果旧节点是空,则是创建阶段
const isDestroy = newVnode === emptyVnode // 新节点是空,则是销毁阶段
var oldDirs = normalizeDirectives$1(oldVnode.data.directives, oldVnode.context);
var newDirs = normalizeDirectives$1(newVnode.data.directives, newVnode.context);
//新增
var dirsWithInsert = [];
var dirsWithPostpatch = [];
var key, oldDir, dir;
// 首先是遍历节点的指令
for (key in newDirs) {
oldDir = oldDirs[key];
dir = newDirs[key];
if (!oldDir) {
// 第一次挂载,老节点是一个的空节点没有该指令,那么指令会执行bind生命周期
callHook$1(dir, 'bind', vnode, oldVnode);
if (dir.def && dir.def.inserted) {
// 如果用户写了inserted生命周期,则先把它暂存在一个dirsWithInsert数组中
dirsWithInsert.push(dir);
}
} else {
dir.oldValue = oldDir.value;
dir.oldArg = oldDir.arg;
// 组件更新阶段,会执行指令的update生命周期
callHook$1(dir, 'update', vnode, oldVnode);
if (dir.def && dir.def.componentUpdated) {
// 如果用户写了componentUpdated生命周期,则先把它暂存在一个componentUpdated数组中
dirsWithPostpatch.push(dir);
}
}
}
if (dirsWithInsert.length) {
// 定义callInsert函数, 该函数里面是去循环执行inserted的生命周期函数
var callInsert = function () {
for (var i = 0; i < dirsWithInsert.length; i++) {
callHook$1(dirsWithInsert[i], 'inserted', vnode, oldVnode);
}
};
if (isCreate) { // 第一次挂载的情况
// mergeVNodeHook:是把callInsert和vnode的insert生命周期合并在一起
// 前面我们有一张图介绍vnode生成真实dom的周期钩子,vnode的insert周期就是当节点被嵌入到父节点后执行的
// callInsert被合并到vnode的insert生命周期,也就是说当节点嵌入到父节点后,callInsert会被执行
// 我们本次不讲callInsert是怎么合并进去,也不讲vnode是怎么去执行到insert的,这不属于指令相关
mergeVNodeHook(vnode, 'insert', callInsert);
} else {
// 如果不是第一次挂载,就不需要合并到vnode的生命周期当中了
// 因为vnode在第一次挂载之后,就不会再执行insert了,合并进去也没用
// 但是指令的insert周期,不只在第一次挂载要执行,每次节点更新都要执行
callInsert();
}
}
if (dirsWithPostpatch.length) {
// 这里的逻辑和上面的差不多,把指令的componentUpdated周期合并到vnode的postpatch生命周期
// vnode的postpatch生命周期上面有介绍到,是新旧节点patch完之后执行的
mergeVNodeHook(vnode, 'postpatch', function () {
for (var i = 0; i < dirsWithPostpatch.length; i++) {
callHook$1(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode);
}
});
}
if (!isCreate) {
for (key in oldDirs) { // 如果旧节点存在该指令,新节点不存在,则指令要被卸载,执行unbind生命周期
if (!newDirs[key]) {
// no longer present, unbind
callHook$1(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy);
}
}
}
}