一、先搞懂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前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。