Vue的nextTick方法源码深度解读

先看看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队列,那么先进入队列的先执行才对。

举个例子:

js 复制代码
let next = Promise.resolve()
let ref = Promise.resolve()
next.then(()=>{
    console.log('先执行')
})
ref.then(()=>{
   console.log('后执行')
})

但是实际使用vue:

js 复制代码
<template>
  <div>{{ msg }}</div>
</template>
<script setup lang="ts">
const msg = ref(1)
nextTick(() => {
  debugger
  console.info("执行")
})  //回调函数推到promise队列(先进入队列应该先执行)
msg.value=4  //渲染函数推到promise队列(后进入队列应该后执行)
</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源码

js 复制代码
const resolvedPromise = /*#__PURE__*/ Promise.resolve() as Promise<any>
let currentFlushPromise: Promise<void> | null = null

const RECURSION_LIMIT = 100
type CountMap = Map<SchedulerJob, number>

export function nextTick<T = void, R = void>(
  this: T,
  fn?: (this: T) => R,
): Promise<Awaited<R>> {
  const p = currentFlushPromise || resolvedPromise
  return fn ? p.then(this ? fn.bind(this) : fn) : p
}

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

js 复制代码
export function queueJob(job: SchedulerJob) {
  if (
    !queue.length ||
    !queue.includes(
      job,
      isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex,
    )
  ) {
    if (job.id == null) {
      queue.push(job)
    } else {
      queue.splice(findInsertionIndex(job.id), 0, job)
    }
    queueFlush()
  }
}

function queueFlush() {
  if (!isFlushing && !isFlushPending) {
    isFlushPending = true
    currentFlushPromise = resolvedPromise.then(flushJobs)
  }
}

找到页面初始化时候的代码:

github1s链接:https://github1s.com/vuejs/core/blob/main/packages/runtime-core/src/renderer.ts

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

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

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

真正的代码执行流程:

js 复制代码
const msg = ref(1)
// 这里会把页面的渲染函数添加到queueJob队列。并且去重。
js 复制代码
nextTick(() => {
  console.info("执行")
}) 

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

js 复制代码
msg.value=4 
// 这里会把页面的渲染函数添加到queueJob队列。并且去重。

总结

本质上nextTick的调用和vue组件执行渲染函数的队列都通过同一个Promose进行调度。

所以才可以实现nextTick延迟执行的效果。

形如:

js 复制代码
let p = Promise.resolve() //类似上面的currentFlushPromise
let reactive = ()=>{console.log('响应式更新')}
let nextTick = ()=>{console.log('nextTick回调')}
p=p.then(reactive)
p=p.then(nextTick)
// 控制台输出:nextTick回调响应式更新
// 控制台输出:nextTick回调
相关推荐
B站计算机毕业设计超人2 分钟前
计算机毕业设计Django+Vue.js高考推荐系统 高考可视化 大数据毕业设计(源码+LW文档+PPT+详细讲解)
大数据·vue.js·hadoop·django·毕业设计·课程设计·推荐算法
YAY_tyy3 分钟前
2025 最新版 Node.js 下载安装及环境配置教程
前端·node.js·教程·工具配置
B站计算机毕业设计超人5 分钟前
计算机毕业设计Django+Vue.js音乐推荐系统 音乐可视化 大数据毕业设计 (源码+文档+PPT+讲解)
大数据·vue.js·hadoop·python·spark·django·课程设计
百思可瑞教育6 分钟前
Vue 前端与 Node.js 后端文件上传与处理实现
前端·javascript·vue.js·前端框架·node.js·ecmascript·百思可瑞教育
架构师汤师爷13 分钟前
一文彻底搞懂 OpenClaw 的架构设计与运行原理(万字图文)
前端·agent
苑若轻航14 分钟前
防抖和节流:解决高频事件性能
前端
小黑的铁粉17 分钟前
什么是事件循环?调用堆栈和任务队列之间有什么区别?
前端·javascript
小黑的铁粉17 分钟前
常见的内存泄漏有哪些?
前端·javascript
喝水的长颈鹿17 分钟前
JavaScript 基础入门
前端
喝咖啡的女孩19 分钟前
call、apply、bind 原理与实现
前端