vue源码分析(二十八)Vue之自定义指令directives

一个指令定义对象可以提供如下几个钩子函数 (均为可选):
bindinsertedupdatecomponentUpdatedunbind
具体的说明请参考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创建节点函数来调用的,我们可以看看整个调用序列图:

image.png

const isCreate = oldVnode === emptyNode
const isDestroy = vnode === emptyNode

可以看到上面定义了两个变量,isCreateisDestroy,通过字面意思我们就可以知道他的用途了。
1、旧的虚拟节点oldVnode是一个空的节点就是新增 (isCreate )
2、新的虚拟节点vnode是一个空的节点就是销毁 (isDestroy )

你可能感兴趣的:(vue源码分析(二十八)Vue之自定义指令directives)