手写 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(); // 执行
  }
}
相关推荐
计算机学姐13 小时前
基于SpringBoot的民宿预定管理系统【三角色+个性化推荐算法+数据可视化统计】
java·vue.js·spring boot·mysql·信息可视化·intellij-idea·推荐算法
VX:Fegn089516 小时前
计算机毕业设计|基于springboot + vue云租车平台系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
小安驾到16 小时前
【前端的坑】vxe-grid表格tooltip提示框不显示bug
前端·vue.js
计算机学姐16 小时前
基于SpringBoot的校园社团管理系统
java·vue.js·spring boot·后端·spring·信息可视化·推荐算法
Liu.77418 小时前
vue开发h5项目
vue.js
pas13618 小时前
42-mini-vue 实现 transform 功能
前端·javascript·vue.js
柒.梧.19 小时前
从零搭建SpringBoot+Vue+Netty+WebSocket+WebRTC视频聊天系统
vue.js·spring boot·websocket
毕设源码-钟学长19 小时前
【开题答辩全过程】以 基于node.js vue的点餐系统的设计与实现为例,包含答辩的问题和答案
前端·vue.js·node.js
小白路过19 小时前
记录vue-cli-service serve启动本地服务卡住问题
前端·javascript·vue.js
梵得儿SHI19 小时前
Vue 高级特性:渲染函数与 JSX 精讲(h 函数语法、JSX 在 Vue 中的应用)
前端·javascript·vue.js·jsx·模板语法·渲染函数·底层视图生成机制