手写 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(); // 执行
  }
}
相关推荐
清幽竹客7 小时前
vue-30(理解 Nuxt.js 目录结构)
前端·javascript·vue.js
知性的小mahua7 小时前
echarts+vue实现中国地图板块渲染+省市区跳转渲染
vue.js
weiweiweb8887 小时前
cesium加载Draco几何压缩数据
前端·javascript·vue.js
满楼、9 天前
el-cascader 设置可以手动输入也可以下拉选择
javascript·vue.js·elementui
前端fighter9 天前
Vuex 与 Pinia:全面解析现代 Vue 状态管理的进化之路
前端·vue.js·面试
snow@li9 天前
vue3-ts-qrcode :安装及使用记录 / 配置项 / 效果展示
前端·javascript·vue.js
海天胜景9 天前
vue3 el-table 根据字段值 改变整行字体颜色
javascript·vue.js·elementui
萌萌哒草头将军9 天前
什么?React 中可以写 SolidJS 了?SolidJS 官方出手了 🚀🚀🚀
前端·vue.js·react.js
用户38022585982410 天前
vue3源码解析:diff算法之节点类型处理机制分析
前端·vue.js
要加油哦~10 天前
vue | vue-macros 插件升级以及配置
前端·javascript·vue.js