先看看vue官网nextTick方法的定义:

nextTick是等待下一次 DOM 更新刷新的工具方法。
当你在 Vue 中更改响应式状态时,最终的 DOM 更新并不是同步生效的,而是由 Vue 将它们缓存在一个队列中,直到下一个“tick”才一起执行。这样是为了确保每个组件无论发生多少状态改变,都仅执行一次更新。
nextTick() 可以在状态改变后立即使用,以等待 DOM 更新完成。你可以传递一个回调函数作为参数,或者 await 返回的 Promise。

根据官网的定义,修改一个响应式数据后,页面dom的更新渲染是异步的。
nextTick方法可以确保页面dom更新后执行回调。

问题来了

vue的响应式数据的修改会触发副作用函数,副作用函数会执行渲染函数。渲染函数使用diff算法更新dom。
这个副作用函数的执行也是先推送到一个异步队列(Promise队列)
但是nextTick本质上也是:Promise.resolve.then(callback)
既然都是promise队列,那么先进入队列的先执行才对。

举个例子:

 1let next = Promise.resolve()
 2let ref = Promise.resolve()
 3next.then(()=>{
 4    console.log('先执行')
 5})
 6ref.then(()=>{
 7   console.log('后执行')
 8})

但是实际使用vue:

 1<template>
 2  <div>{{ msg }}</div>
 3</template>
 4<script setup lang="ts">
 5const msg = ref(1)
 6nextTick(() => {
 7  debugger
 8  console.info("执行")
 9})  
10msg.value=4  
11</script>

实际结果是 页面渲染了4。才执行nextTick的回调函数。

很明显vue做了处理。让nextTick的执行在渲染函数的后面。但是大家都是promise凭啥后执行的能在前面执行?

深入源码看看具体实现:

调度器源码路径:packages/runtime-core/src/scheduler.ts
github1s链接:https://github1s.com/vuejs/core/blob/main/packages/runtime-core/src/scheduler.ts

找到nextTick源码

 1const resolvedPromise =  Promise.resolve() as Promise<any>
 2let currentFlushPromise: Promise<void> | null = null
 3
 4const RECURSION_LIMIT = 100
 5type CountMap = Map<SchedulerJob, number>
 6
 7export function nextTick<T = void, R = void>(
 8  this: T,
 9  fn?: (this: T) => R,
10): Promise<Awaited<R>> {
11  const p = currentFlushPromise || resolvedPromise
12  return fn ? p.then(this ? fn.bind(this) : fn) : p
13}

找到queueJob、queueFlush、flushJobs源码
flushJobs 函数代表从缓存队列里面执行响应式数据对应的副作用函数(渲染dom)。

 1export function queueJob(job: SchedulerJob) {
 2  if (
 3    !queue.length ||
 4    !queue.includes(
 5      job,
 6      isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex,
 7    )
 8  ) {
 9    if (job.id == null) {
10      queue.push(job)
11    } else {
12      queue.splice(findInsertionIndex(job.id), 0, job)
13    }
14    queueFlush()
15  }
16}
17
18function queueFlush() {
19  if (!isFlushing && !isFlushPending) {
20    isFlushPending = true
21    currentFlushPromise = resolvedPromise.then(flushJobs)
22  }
23}

找到页面初始化时候的代码:
github1s链接:https://github1s.com/vuejs/core/blob/main/packages/runtime-core/src/renderer.ts

看一下渲染一个组件的测试用例:

渲染一个组件的执行链路是:
调用createRenderer -> baseCreateRenderer 会返回一个渲染函数 render(渲染函数,传入虚拟节点对象和要挂载的节点)

上图可以看到。vue组件初始化渲染的时候就会把update(副作用函数)传递到queueJob方法内、并且放到queue内。然后执行queueFlush方法。

真正的代码执行流程:

 1const msg = ref(1)
 2
 1nextTick(() => {
 2  console.info("执行")
 3}) 

执行nextTick之前currentFlushPromise已经变成了resolvedPromise.then(flushJobs)
那么nextTick的执行自然只能等flushJobs执行完后执行

 1msg.value=4 
 2

总结

本质上nextTick的调用和vue组件执行渲染函数的队列都通过同一个Promose进行调度。
所以才可以实现nextTick延迟执行的效果。

形如:

 1let p = Promise.resolve() 
 2let reactive = ()=>{console.log('响应式更新')}
 3let nextTick = ()=>{console.log('nextTick回调')}
 4p=p.then(reactive)
 5p=p.then(nextTick)
 6
 7
个人笔记记录 2021 ~ 2025