vue指令本质
指令本质上是装饰器,是vue对HTML元素的扩展,给HTML元素增加自定义功能,语义化HTML标签。vue编译DOM时,会执行与指令关联的JS代码,即找到指令对象,执行指令对象的相关方法。
自定义指令生命周期
自定义指令有五个生命周期(也叫钩子函数),分别是bind、inserted、update、componentUpdated、unbind
钩子函数作用介绍
- bind:只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个绑定时执行一次的初始化动作。
- inserted:被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于document中)。
- update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新。
- componentUpdated:被绑定元素所在模板完成一次更新周期时调用。
- unbind:只调用一次,指令与元素解绑时调用。
实现过程
源码
// 版本2.6.10 export default {
create: updateDirectives, update: updateDirectives, destroy: function unbindDirectives (vnode: VNodeWithData) {
updateDirectives(vnode, emptyNode) } } 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 // 当新的虚拟节点不存在,在旧虚拟节点存在时,为true const oldDirs = normalizeDirectives(oldVnode.data.directives, oldVnode.context) // 旧指令集合 const newDirs = normalizeDirectives(vnode.data.directives, vnode.context) // 新指令集合 const dirsWithInsert = [] // 保存需要触发inserted指令钩子的列表 const dirsWithPostpatch = [] // 保存需要触发componentUpdated指令钩子的列表 let key, oldDir, dir for (key in newDirs) {
oldDir = oldDirs[key] dir = newDirs[key] if (!oldDir) {
// 判断oldDir是否存在,如果不存在,则首次绑定到元素中 // 调用bind callHook(dir, 'bind', vnode, oldVnode) // 判断指令是否有inserted方法,有则添加到dirsWithInsert,保证执行完指令的bind方法后执行inserted方法 if (dir.def && dir.def.inserted) {
dirsWithInsert.push(dir) } } else {
// oldDir存在,则更新指令 dir.oldValue = oldDir.value dir.oldArg = oldDir.arg callHook(dir, 'update', vnode, oldVnode) // 判断指令是否有componentUpdated方法,有则添加到dirsWithPostpatch, // 保证指令所在的vnode及自vnode更新完后(执行完指令的update方法后),执行componentUpdated方法 if (dir.def && dir.def.componentUpdated) {
dirsWithPostpatch.push(dir) } } } if (dirsWithInsert.length) {
const callInsert = () => {
for (let i = 0; i < dirsWithInsert.length; i++) {
callHook(dirsWithInsert[i], 'inserted', vnode, oldVnode) } } if (isCreate) {
// 如果是新创建的节点,使用mergeVNodeHook将一个钩子函数与虚拟节点现有的钩子函数合并在一起 // 可以将钩子函数的执行推迟到被绑定的元素插入到父节点之后进行 mergeVNodeHook(vnode, 'insert', callInsert) } else {
callInsert() } } if (dirsWithPostpatch.length) {
mergeVNodeHook(vnode, 'postpatch', () => {
for (let i = 0; i < dirsWithPostpatch.length; i++) {
callHook(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode) } }) } // 先判断当前虚拟节点是否是新创建 if (!isCreate) {
// 循环旧指令集合,找出不存在的,则该指令是废弃的,并执行指令的unbind方法 for (key in oldDirs) {
if (!newDirs[key]) {
callHook(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy) } } } } const emptyModifiers = Object.create(null) function normalizeDirectives ( dirs: ?Array<VNodeDirective>, vm: Component ): {
[key: string]: VNodeDirective } {
const res = Object.create(null) if (!dirs) {
return res } let i, dir for (i = 0; i < dirs.length; i++) {
dir = dirs[i] if (!dir.modifiers) {
dir.modifiers = emptyModifiers } res[getRawDirName(dir)] = dir dir.def = resolveAsset(vm.$options, 'directives', dir.name, true) } return res } function getRawDirName (dir: VNodeDirective): string {
return dir.rawName || `${
dir.name}.${
Object.keys(dir.modifiers || {
}).join('.')}` } function callHook (dir, hook, vnode, oldVnode, isDestroy) {
const fn = dir.def && dir.def[hook] if (fn) {
try {
fn(vnode.elm, dir, vnode, oldVnode, isDestroy) } catch (e) {
handleError(e, vnode.context, `directive ${
dir.name} ${
hook} hook`) } } }
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/224091.html原文链接:https://javaforall.net
