Code前端首页关于Code前端联系我们

一、先搞懂Vue2的nextTick是干啥的?

terry 7小时前 阅读数 9 #Vue
文章标签 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前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门