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回调
相关推荐
cc.ChenLy2 小时前
【CSS进阶】毛玻璃效果与代码解析
前端·javascript·css
何中应2 小时前
使用Jenkins部署前端项目(Vue)
前端·vue.js·jenkins
3秒一个大2 小时前
JWT 登录:原理剖析与实战应用
前端·http·代码规范
NEXT062 小时前
2026 技术风向:为什么在 AI 时代,PostgreSQL 彻底成为了全栈工程师的首选数据库
前端·数据库·ai编程
NEXT063 小时前
拒绝“盲盒式”编程:规范驱动开发(SDD)如何重塑 AI 交付
前端·人工智能·markdown
@大迁世界3 小时前
仅用 CSS 实现元素圆形排列的方法
前端·css
JosieBook4 小时前
【Vue】15 Vue技术——Vue计算属性简写:提升代码简洁性的高效实践
前端·javascript·vue.js
rfidunion4 小时前
springboot+VUE+部署(11。Nginx)
linux·vue.js·nginx
weixin199701080164 小时前
Lazada商品详情页前端性能优化实战
前端·性能优化
星火开发设计5 小时前
异常规范与自定义异常类的设计
java·开发语言·前端·c++