手写 mini-vue3 实现 nextTick(十二)

原理

可以这么来理解 nextTick,响应式数据发生改变是同步任务,nextTick 是异步微任务,根据 JS 的事件循环原理,是先执行同步任务,再执行微任务。

假如没有使用 nextTick 会怎样呢?如果不断触发响应式数据的改变,那么就会不断地触发 effect 副作用函数,导致多次执行 render 函数,而我们只是想要最终的那一次更新即可,中间过程的更新对于我们只想渲染最终结果到页面来说是多余的触发。

那么问题就是,怎么才可以在等响应式数据改变完之后,再去执行且只执行一次 render 函数呢?

异步微任务可以做到。

在响应式数据发生改变的时候,不执行 effect 副作用函数 fn,而是通过 scheduler 把当前的 fninstance.update 添加到异步微任务队列。

等到所有同步的任务执行完,也就是先让响应式数据最终改变完成后,再去执行异步微任务队列的任务。

而在执行了 instance.update 之后,DOM 的数据才是最新的。

vue3 抛出的 nextTick 内部的实现原理是接收一个函数 fn,返回一个 promise,该 promisePromise.resolve().then(fn) 的结果,也就是它也是内部实现的是一个异步微任务。

所以我们往往使用 nextTick 来获取 DOM 更新之后的数据。

这个例子也许可以帮我们更好地理解 JS 的微任务在 nextTick 中的使用:

js 复制代码
const instanceUpdate = () => {
  console.log('effect-update')
}
const p1 = Promise.resolve();
const queue1 = [];

function queueJobFn(job) {
  if(queue1.includes(job)) {
    return;
  }

  queue1.push(job);

  p1.then(() => {
    
    while(queue1.length) {
      const job = queue1.shift();
      job && job();
    }
  });
}

let i = 1; // 模拟响应式数据
i++; // 模拟响应式数据发生变化
queueJobFn(instanceUpdate); // 模拟触发 effect scheduler
i++; // 模拟响应式数据发生变化
queueJobFn(instanceUpdate); // 模拟触发 effect scheduler

// 模拟 nextTick
p1.then(() => {
  console.log(i);
});

执行的顺序是:

  1. i = 1,执行同步
  2. i++,执行同步,i = 2
  3. queueJobFn(instanceUpdate); 依据 JS 事件循环,内部遇到 Promise.then,先不执行,放到微任务队列中排队等候,而我们自己也把任务存到 queue1 队列中。
  4. i++,执行同步,i = 3
  5. queueJobFn(instanceUpdate); queue1 队列中已存在于 instanceUpdate,return
  6. 遇到 p1.then 微任务,依据 JS 事件循环,遇到 Promise.then,先不执行,放到微任务队列中排队等候
  7. 依据 JS 事件循环,同步任务执行完,开始执行异步微任务,先进入队列的是 instanceUpdate,执行,从 queue1 中一一取出任务来执行,打印 effect-update
  8. 接着还有 p1.then, 执行,打印 3

实现

scheduler

diff 复制代码
  function setupRenderEffect(instance, initialVNode, container, anchor) {
    instance.update = effect(() => {
      if(!instance.isMounted) {
        console.log('init');
        const { proxy } = instance;
        const subTree = (instance.subTree = instance.render.call(proxy, proxy));
        
        patch(null, subTree, container, instance, anchor);
      
        // vnode -> element
        initialVNode.el = subTree.el;

        instance.isMounted = true;
      } else {
        console.log('update');

        // 更新 props,需要一个 vnode
        const { next, vnode } = instance;
        if(next) {
          // 设置 新虚拟节点的 el
          next.el = vnode.el;
          updateComponentPreRender(instance, next);
        }

        const { proxy } = instance;
        const subTree = instance.render.call(proxy, proxy);
        const prevSubTree = instance.subTree;
        instance.sbuTree = subTree;

        patch(prevSubTree, subTree, container, instance, anchor);
      }
+    }, {
+      scheduler() {
+        queueJobs(instance.update); 
+      }
+    });
  }

queueJobs

queueJobs 的作用是把 instance.update 添加到微任务队列,等同步任务执行完之后,再执行 instance.update

外面手动添加 nextTick 写异步回调,则这个异步回调将会以异步微任务的形式执行。

如果要回获取 DOM 渲染后的数据,则在 nextTick 回调中可以获取到。

ts 复制代码
// scheduler.ts
const queue: any[] = []; // 微任务队列

const p = Promise.resolve(); // 可以防止多次创建
let isFlushPending = false;

export function nextTick(fn?) {
  return fn ? p.then(fn) : p; // 返回值是个 promise
}

export function queueJobs(job) {
  // 如果已经存在,则不需要再次添加,不存在则添加到队列
  if(!queue.includes(job)) {
    queue.push(job);
  }

  queueFlush();
}

function queueFlush () {
  if(isFlushPending) return; // 有在执行微任务,则返回

  isFlushPending = true;

  nextTick(flushJobs); // 微任务
}

function flushJobs() {
  isFlushPending = false;

  let job;
  while(job = queue.shift()) { // 出队
    job && job(); // 执行
  }
}
相关推荐
小彭努力中3 小时前
199.Vue3 + OpenLayers 实现:点击 / 拖动地图播放音频
前端·vue.js·音视频·openlayers·animate
慧一居士5 小时前
Vue项目中,何时使用布局、子组件嵌套、插槽 对应的使用场景,和完整的使用示例
前端·vue.js
Jave21086 小时前
实现全局自定义loading指令
前端·vue.js
慧一居士10 小时前
Vue项目中,子组件调用父组件方法示例,以及如何传值示例,对比使用插槽和不使用插槽区别
前端·vue.js
SeSs IZED11 小时前
【Nginx 】Nginx 部署前端 vue 项目
前端·vue.js·nginx
SuperEugene11 小时前
Vue3 性能优化规范:日常必做优化(不玄学、可落地)|可维护性与兜底规范篇
开发语言·前端·javascript·vue.js·性能优化·前端框架
A923A12 小时前
【小兔鲜电商前台 | 项目笔记】第二天
前端·vue.js·笔记·项目·小兔鲜
happymaker062612 小时前
vue指令扩展以及监视器的使用
前端·javascript·vue.js
还是大剑师兰特12 小时前
EventBus核心方法用法
javascript·vue.js·大剑师
happymaker062614 小时前
vue中对list的函数处理方式总结,以及常见功能的实现方法
javascript·vue.js·list