Vue指令的奥秘

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对象里面的函数就是指令的周期函数,来看下这些周期函数的执行时机:
  1. vnode的create阶段:
    1.  执行bind
    2.  执行insert
  1. vnode的update阶段:
    1.  执行update
    2.  执行insert
    3.  执行componentUpdated
  1. 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);
      }
    }
  }
}

Peng_YT的掘金

Peng_YT的语雀

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值