一、先搞懂Vue2的nextTick是干啥的?
p>开发Vue项目时,有没有遇到过「数据改了,DOM却没及时更新,操作DOM时拿到旧值」的情况?这时候不少人会想到 this.$nextTick,但给它加上 await 后有啥不一样?哪些场景必须用?今天就把Vue2里 await nextTick 的门道聊透。
Vue的响应式更新是“异步”的,比如你修改了 data 里的变量,Vue不会立刻更新DOM,而是把所有更新操作塞进一个「异步队列」里,等当前同步任务跑完,再统一更新DOM,这么做是为了批量处理DOM操作,减少不必要的重绘重排,提升性能。
而 nextTick 就是让我们把回调函数“插队”到这个异步队列的末尾,等DOM更新完成后再执行回调,举个简单例子:
// 数据更新
this.msg = '新内容'
// 此时DOM还没更新,直接拿DOM内容是旧的
console.log(document.getElementById('box').innerText) // 旧值
this.$nextTick(() => {
// 等DOM更新后,这里才能拿到新值
console.log(document.getElementById('box').innerText) // 新值
})
给nextTick加await有啥特殊作用?
普通的 this.$nextTick(回调) 是回调函数风格,而加 await 后,是把 nextTick 当成Promise来用(Vue2里 this.$nextTick() 不传回调时,会返回一个Promise),用 async/await 语法,可以让代码“暂停”到DOM更新后再继续执行,代码结构更简洁。
对比两种写法感受下:
// 回调式:多层嵌套容易变成“回调地狱”
this.$nextTick(() => {
this.doSomething1()
this.$nextTick(() => {
this.doSomething2()
})
})
// async/await式:代码像“同步”一样流畅
async handleClick() {
this.msg = '更新后'
await this.$nextTick() // 暂停,等DOM更新
this.doSomething1()
await this.$nextTick() // 再等一次DOM更新(如果有需要)
this.doSomething2()
}
用 await 的好处很明显:避免回调嵌套,在处理复杂异步逻辑时(比如和接口请求串联),代码可读性更高,和其他Promise风格的代码更统一。
哪些真实场景必须用await nextTick?
不是所有场景都要加 await,但遇到这三类情况,不用它大概率会踩坑:
数据更新后立刻操作DOM
比如做「点击按钮,输入框自动聚焦」的功能,输入框的显示由 v-show 控制,数据变化后DOM不会立刻更新,直接调 focus 会失效:
<template>
<button @click="showInput">显示输入框并聚焦</button>
<input v-show="isShow" ref="inputRef" />
</template>
<script>
export default {
data() { return { isShow: false } },
methods: {
async showInput() {
this.isShow = true
// 此时input可能还没渲染(v-show切换是异步的)
await this.$nextTick() // 等DOM更新后再聚焦
this.$refs.inputRef.focus()
}
}
}
</script>
组件更新后初始化第三方库
比如用ECharts做图表,数据是异步获取的,数据更新后,DOM容器的尺寸可能变化,必须等DOM更新后再初始化ECharts:
async mounted() {
const res = await axios.get('/api/data')
this.chartData = res.data // 数据更新(异步)
await this.$nextTick() // 等DOM根据新数据更新
this.initECharts() // 此时能拿到最新的DOM容器尺寸
}
列表渲染后计算高度/位置
动态渲染列表时,新数据push后DOM还没渲染,立刻计算高度会拿到旧值:
async addItem() {
this.list.push({ text: '新项' }) // 数据更新
await this.$nextTick() // 等新列表项渲染到DOM
const scrollHeight = this.$refs.list.scrollHeight
console.log('最新滚动高度:', scrollHeight)
}
await nextTick和普通nextTick有啥区别?
虽然最终都是等DOM更新,但写法和使用场景有明显差异:
| 对比维度 | 普通this.$nextTick(回调) |
await this.$nextTick() |
|---|---|---|
| 语法风格 | 回调函数风格 | Promise + async/await风格 |
| 代码复杂度 | 多层嵌套易成“回调地狱” | 线性执行,可读性高 |
| 错误处理 | 需在回调内写try...catch | 可在外层统一用try...catch捕获 |
| 适用场景 | 简单场景(单一层级回调) | 复杂异步逻辑(多步骤依赖DOM) |
执行时机完全一致:都是等Vue的异步更新队列执行完、DOM更新后,再执行后续代码,区别只在写法和代码组织方式。
用await nextTick要避开哪些坑?
想用得顺手,这几个细节要注意:
别滥用!非必要不用
nextTick 本质是“等异步队列”,频繁用会让代码执行顺序变复杂,还可能拖慢性能。只在确实需要等DOM更新时用(比如上面举的操作DOM、初始化库的场景)。
注意Promise兼容
Vue2里 this.$nextTick() 返回的Promise,需要环境支持Promise(比如IE浏览器不支持,要手动加polyfill),如果项目没处理兼容,用 await 可能报错,这时候要么加polyfill,要么换回回调式。
生命周期里的时机要分清
比如在 created 钩子中,DOM还没挂载,这时候用 nextTick 是等初次DOM渲染;如果是 mounted 里数据更新后用,是等数据更新后的DOM更新,要根据场景判断DOM状态。
错误处理不能漏
await 后面跟的是Promise,一旦 nextTick 内部出错(比如回调里抛错),会触发reject,所以要用 try...catch 包起来:
async handle() {
try {
await this.$nextTick()
// 执行依赖DOM的操作
} catch (err) {
console.error('nextTick出错:', err)
}
}
Vue2 nextTick的原理是啥?(懂原理更敢用)
Vue2的异步更新队列,优先用“微任务”(比如Promise.then、MutationObserver),如果环境不支持微任务,才用“宏任务”(比如setTimeout)。nextTick 就是把回调函数塞到这个队列里。
当你调用 this.$nextTick() 时,Vue内部逻辑简化后大概是这样:
Vue.prototype.$nextTick = function (cb) {
// 创建一个Promise
let res = Promise.resolve()
// 把回调放到微任务队列
res.then(() => {
if (cb) cb() // 执行用户传的回调
})
// 如果没传cb,返回这个Promise,方便await
if (!cb) return res
}
实际Vue的实现更复杂(要处理不同环境的异步策略),但核心逻辑是:把回调放到异步队列,等当前同步代码跑完,再执行回调,确保DOM已经更新。await nextTick 其实就是等这个异步队列执行完,再继续后续代码。
Vue2里的 await nextTick,核心是用Promise语法让我们更优雅地“卡点”——等DOM更新后再执行代码,不管是操作DOM、初始化第三方库,还是处理列表渲染后的逻辑,只要遇到「数据更新了,DOM没跟上」的问题,它都能精准解决,但记住别滥用,只在真需要的时候用,同时注意Promise兼容和错误处理,才能把这个工具用得顺手~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网


