深入了解Vue中nextTick

631 阅读2分钟

Vue中nextTick的使用

在vue中开发当中,我们有时需要拿到dom元素更新以后的信息,当我们给变量赋值以后立即去获取dom元素信息,通常是获取到的是更新以前的,在此需要借助nextTick这个api。官方示例如下

// 修改数据
vm.msg = 'Hello' 
// 此处DOM 还没有更新 

// 推荐
Vue.nextTick(function () { 
    // 此处 DOM 更新了 
}) 

// 不推荐
Vue.nextTick().then(function () {
    // 此处 DOM 更新了 
})

此处推荐第一种写法,在nextTick原理上,不一定返回一个Promise,也有可能是通过MutationObserver、setImmediate或setTimeout实现。

Vue.nextTick()这个方法在事件循环当中,不一定是微任务或者宏任务,其原理上的Promise和MutationObserver属于微任务,而setImmediate和setTimeout属于宏任务。

Vue中nextTick的原理解析

当我们在Vue中使用nextTick()时,会先定义好callbacks数组(把需要在dom元素更新以后执行的内容放到callbacks队列当中,以供dom元素更新以后再执行),pending状态(判断是否是在等待状态,避免多次执行),flushCallbacks()函数(把存入callbacks中的内容全部执行一遍),timerFunc()函数(根据浏览器对Promise、MutationObserver、setImmediate和setTimeout的支持情况,定义为不同的内容),nextTick()函数(往callbacks中添加回调内容,并调用timerFunc执行对应回调)。关键源码如下所示:

  var callbacks = [];
  var pending = false;
  var timerFunc;
  
  function flushCallbacks () {
    pending = false;
    var copies = callbacks.slice(0);
    callbacks.length = 0;
    for (var i = 0; i < copies.length; i++) {
      copies[i]();
    }
  }
  
  if (typeof Promise !== 'undefined' && isNative(Promise)) {
    var p = Promise.resolve();
    timerFunc = function () {
      p.then(flushCallbacks);
      if (isIOS) { setTimeout(noop); }
    };
    isUsingMicroTask = true;
  } else if (!isIE && typeof MutationObserver !== 'undefined' && (
    isNative(MutationObserver) ||
    MutationObserver.toString() === '[object MutationObserverConstructor]'
  )) {
    var counter = 1;
    var observer = new MutationObserver(flushCallbacks);
    var textNode = document.createTextNode(String(counter));
    observer.observe(textNode, {
      characterData: true
    });
    timerFunc = function () {
      counter = (counter + 1) % 2;
      textNode.data = String(counter);
    };
    isUsingMicroTask = true;
  } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
    timerFunc = function () {
      setImmediate(flushCallbacks);
    };
  } else {
    timerFunc = function () {
      setTimeout(flushCallbacks, 0);
    };
  }
  
  function nextTick (cb, ctx) {
    var _resolve;
    callbacks.push(function () {
      if (cb) {
        try {
          cb.call(ctx);
        } catch (e) {
          handleError(e, ctx, 'nextTick');
        }
      } else if (_resolve) {
        _resolve(ctx);
      }
    });
    if (!pending) {
      pending = true;
      timerFunc();
    }
    if (!cb && typeof Promise !== 'undefined') {
      return new Promise(function (resolve) {
        _resolve = resolve;
      })
    }
  }
执行步骤:
  • 调用nextTick (cb, ctx)函数,向callbacks中添加对应的回调
  • 判断当前pending是否是false,如果是的话,改为true,同时调用timerFunc()函数(该函数具体是Promise、MutationObserver、setImmediate、setTimeout中的哪种,看代码中对该浏览器是否支持该方式来判断,涉及到的isIOS、isIE、isNative等函数均在vue原理上已经封装好)
  • 当cb不存在时,返回一个Promise函数
  • 执行flushCallbacks(),执行添加到callbacks中的函数(此时dom已经更新)
方法推荐:

推荐做一个demo,把vue源码下载下来,在nextTick()中打上debugger,进行单步调试,查看源码运行过程。