是什么
nextTick 是 Vue 提供的一个全局 API,它接收一个回调函数,并确保这个回调在下一次 DOM 更新循环结束之后执行。简单来说,它让你能够“等待” Vue 完成对 DOM 的更新,然后执行你的代码。
在 Vue 3 中,可以通过 import { nextTick } from 'vue' 导入使用。
为什么需要 nextTick
Vue 采用异步更新队列的策略来提升性能。当你修改响应式数据时,Vue 并不会立即更新 DOM,而是将这次更新操作推入一个队列中。如果在同一个“事件循环 tick”中多次修改数据,这些修改会被合并,最终只触发一次 DOM 更新。这样做可以避免不必要的计算和渲染,提高性能。
但由于 DOM 更新是异步的,如果你在修改数据后立即尝试读取 DOM 的状态(比如元素的高度、文本内容等),你得到的可能还是更新前的旧值。nextTick 就是用来解决这个问题的——它让你可以在 DOM 更新完成后执行回调,从而获取到最新的 DOM 状态。
使用 nextTick 解决上面问题
<template>
<view>
<view id="counter">{{ count }}</view>
<button @click="showDOM">点我</button>
</view>
</template>
<script setup>
import { ref, nextTick } from 'vue'
const count = ref(0)
const showDOM = async () => {
count.value = 5 // 修改数据
// ❌ 错误:立即获取
console.log('立即获取:', document.getElementById('counter').textContent) // 0
// ✅ 正确:等待DOM更新后再获取
nextTick(()=>{
console.log('nextTick后:', document.getElementById('counter').textContent) // 5
})
console.log('这个时候nextTick还么执行:', document.getElementById('counter').textContent)
}
</script>
日志如下:
19:24:24.231 立即获取: 0 at pages/nextTick/nextTick.vue:17
19:24:24.239 这个时候nextTick还么执行: 0 at pages/nextTick/nextTick.vue:23
19:24:24.239 nextTick后: 5 at pages/nextTick/nextTick.vue:21
nextTick 的工作原理
// 简化版理解
const add = () => {
count.value = 5 // 1. 修改数据
// 2. Vue把DOM更新任务放到队列
// queue: [更新count的DOM]
// 3. 同步代码继续执行
console.log('数据已改,但DOM未更新') // 立即执行
nextTick(() => {
// 4. 等同步代码执行完,DOM更新完成后
// 这里的代码才执行
console.log('DOM已更新,可以安全操作')
})
// 5. 同步代码结束
}
// 执行顺序:
// 1. 修改数据 count.value = 5
// 2. 执行 console.log('数据已改,但DOM未更新')
// 3. 注册 nextTick 回调
// 4. 同步代码结束
// 5. Vue开始更新DOM (0变成5)
// 6. 执行 nextTick 回调
基本语法形式
回调函数形式
import { nextTick } from 'vue'
// 基本用法
nextTick(() => {
// DOM更新后执行的代码
console.log('DOM已更新')
})
// 这里的代码会立即执行,不会等待
// 带参数的用法
nextTick((arg1, arg2) => {
console.log('参数:', arg1, arg2)
}, '参数1', '参数2') // 额外的参数会传给回调函数
// 这里的代码会立即执行,不会等待
Promise 形式(推荐)
import { nextTick } from 'vue'
// 使用 .then()
nextTick().then(() => {
// 这里的代码会在DOM更新后执行
console.log('DOM已更新')
})
// 这里的代码会立即执行,不会等待
// 使用 async/await(最清晰)
const handleClick = async () => {
count.value++
await nextTick() // 等待DOM更新
// 这里的代码会在DOM更新后执行
console.log('DOM已更新,可以安全操作')
// 后面的代码也会等待
}
nextTick 执行时机
| 场景 | nextTick 执行时机 |
执行次数 |
|---|---|---|
| 组件创建时注册 | 首次DOM渲染完成后 | 1次 |
| 点击事件中注册 | 本次点击导致的DOM更新完成后 | 每次点击1次 |
| 没有DOM更新 | 永远不会执行 | 0次 |
| 多次数据变化 | 所有变化合并后的那次DOM更新 | 1次 |
| 多次注册 | 按注册顺序执行 | 对应那次DOM更新 |
组件创建时的 nextTick
<script setup>
import { ref, nextTick } from 'vue'
console.log('1. 组件开始创建')
// 这个 nextTick 只会在首次DOM更新后执行一次
nextTick(() => {
console.log('3. 首次DOM更新完成')
})
console.log('2. 组件创建完成')
</script>
<template>
<view>模板内容</view>
</template>
// 输出顺序:
// 1. 组件开始创建
// 2. 组件创建完成
// (模板渲染)
// 3. 首次DOM更新完成 ← 只执行一次
事件处理中的 nextTick
<template>
<view>
<view id="counter">{{ count }}</view>
<button @click="handleClick">点击</button>
</view>
</template>
<script setup>
import { ref, nextTick } from 'vue'
const count = ref(0)
const handleClick = () => {
count.value++ // 修改数据
// 这个 nextTick 会在本次点击导致的DOM更新后执行
nextTick(() => {
console.log('DOM已更新,新值为:', document.getElementById('counter').textContent)
})
}
// 点击第一次输出:
// DOM已更新,新值为: 1
// 点击第二次输出:
// DOM已更新,新值为: 2
// 每次点击都会执行这个 nextTick
</script>
多个 nextTick 的执行顺序
<script setup>
import { ref, nextTick } from 'vue'
const count = ref(0)
// nextTick 1
nextTick(() => {
console.log('nextTick 1')
})
// nextTick 2
nextTick(() => {
console.log('nextTick 2')
})
// nextTick 3
nextTick(() => {
console.log('nextTick 3')
})
const handleClick = () => {
count.value++
// nextTick 4
nextTick(() => {
console.log('nextTick 4')
})
// nextTick 5
nextTick(() => {
console.log('nextTick 5')
})
}
// 页面加载后输出:
// nextTick 1
// nextTick 2
// nextTick 3
// 点击按钮后输出:
// nextTick 4
// nextTick 5
</script>