Vue 的 DOM 更新竟然是异步的?90%的人没有搞懂 nextTick

刚接触Vue那会儿,以为修改数据后DOM会立刻更新,结果踩了不少坑。相信不少小伙伴也有过这样的经历,今天来分享一下这个让人困惑的问题。

场景

假设我们有这样一个简单的组件:

html 复制代码
<template>
  <div>
    <button @click="updateData">点击我</button>
    <p ref="content">{{ message }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: '初始消息'
    }
  },
  methods: {
    updateData() {
      this.message = '更新后的消息'
      console.log('DOM内容:', this.$refs.content.textContent)
    }
  }
}
</script>

猜猜点击按钮后,控制台会输出什么? 是"更新后的消息 "吗?不是!实际输出的是"初始消息"!

为什么会这样?

原来,VueDOM更新不是同步的,而是异步的 !当我们修改数据时,Vue并不会立即去更新DOM,而是将这些更新操作放入一个队列中,在下一个事件循环中才统一执行。

这就好比你去餐厅点餐,服务员不会每点一道菜就跑去厨房一次,而是等你点完所有菜后,才把整个菜单交给厨房。

实际开发中的问题

我曾经在做项目时遇到过这样的问题:

javascript 复制代码
updateData() {
  this.message = '新消息'
  this.isShow = true
  // 想要操作更新后的DOM
  this.$nextTick(() => {
    // 这样才能拿到更新后的DOM
    this.$refs.content.style.color = 'red'
  })
}

如果不使用nextTick,直接操作DOM,很可能操作的是更新前的状态,导致效果不符合预期。

nextTick的使用

nextTick是Vue提供的一个方法,它会在DOM更新完成后执行回调函数。常见使用场景:

1. 操作更新后的DOM

javascript 复制代码
this.message = '更新了'
this.$nextTick(() => {
  // 这里可以安全地操作更新后的DOM
  console.log(this.$refs.element.textContent) // 输出:更新了
})

2. 等待视图更新后再执行操作

javascript 复制代码
this.list.push(newItem)
this.$nextTick(() => {
  // 滚动到最新添加的项目
  window.scrollTo(0, this.$refs.list.scrollHeight)
})

3. 在组件更新后执行操作

javascript 复制代码
this.visible = true
this.$nextTick(() => {
  // 此时组件已经渲染完成,可以获取DOM节点
  this.$refs.input.focus()
})

Vue内部使用了一种巧妙的机制来批量处理DOM更新:

  1. 数据变化时,Vue开启一个队列
  2. 同一个事件循环内的数据变化会被批量处理
  3. 在下一个事件循环中,Vue刷新队列并执行实际DOM更新

这样做的好处是避免不必要的重复渲染,提高性能。

Vue3的nextTick用法

上面讲了Vue2的DOM异步更新,那Vue3呢?DOM更新也是异步吗?nextTick用法有变化吗?

没错,Vue3继承了Vue2的异步更新机制,而且原理基本相同。但是用法上有一些小变化。

同样的场景,Vue3中试试

html 复制代码
<template>
  <div>
    <button @click="updateData">点击我</button>
    <p ref="content">{{ message }}</p>
  </div>
</template>

<script>
import { ref, nextTick } from 'vue'

export default {
  setup() {
    const message = ref('初始消息')
    const content = ref(null)

    const updateData = async () => {
      message.value = '更新后的消息'
      console.log('DOM内容:', content.value?.textContent) // 还是"初始消息"!
      
      // Vue3的用法
      nextTick(() => {
        console.log('nextTick中的DOM内容:', content.value.textContent) // "更新后的消息"
      })
    }

    return {
      message,
      content,
      updateData
    }
  }
}
</script>

结果和Vue2一模一样!DOM更新还是异步的。

Vue3中nextTick的变化

1. 引入方式变了

javascript 复制代码
// Vue2
this.$nextTick(() => {
  // 操作DOM
})

// Vue3
import { nextTick } from 'vue'

nextTick(() => {
  // 操作DOM
})

2. 支持async/await

这是Vue3的一个很酷的新特性:

javascript 复制代码
import { ref, nextTick } from 'vue'

const message = ref('初始消息')
const content = ref(null)

const updateData = async () => {
  message.value = '更新后的消息'
  
  // 等待DOM更新完成
  await nextTick()
  
  // 这里可以安全操作DOM了
  console.log(content.value.textContent) // "更新后的消息"
  content.value.style.color = 'red'
}

await nextTick()让代码看起来更简洁。

Vue3实际案例

例1:自动聚焦输入框

javascript 复制代码
import { ref, nextTick } from 'vue'

const showInput = ref(false)
const inputRef = ref(null)

const openInput = async () => {
  showInput.value = true
  await nextTick()
  inputRef.value.focus() // 确保input已经渲染出来
}

例2:列表更新后滚动到底部

javascript 复制代码
import { ref, nextTick } from 'vue'

const messages = ref([])
const listRef = ref(null)

const addMessage = async (text) => {
  messages.value.push(text)
  await nextTick()
  // 滚动到最新消息
  listRef.value.scrollTop = listRef.value.scrollHeight
}

例3:动画效果

javascript 复制代码
import { ref, nextTick } from 'vue'

const isVisible = ref(false)
const elementRef = ref(null)

const showWithAnimation = async () => {
  isVisible.value = true
  await nextTick()
  // 确保元素已经渲染,然后添加动画
  elementRef.value.classList.add('fade-in')
}

Vue3setup函数中,如果你喜欢用Options API的风格,也可以这样用:

javascript 复制代码
import { getCurrentInstance } from 'vue'

const { proxy } = getCurrentInstance()
proxy.$nextTick(() => {
  // 和Vue2一样的用法
})

不过还是推荐直接使用import { nextTick } from 'vue'的方式,更符合Vue3的风格。

为什么Vue3还要保持异步更新?

其实原因和Vue2一样:

  1. 性能优化 - 批量处理更新,避免重复渲染
  2. 避免不必要的计算 - 多次数据变化只进行一次DOM更新
  3. 保证数据一致性 - 在同一事件循环中的所有变化一起处理

需要注意的点

  1. Composition API中nextTick需要手动引入
  2. Options API中 ,仍然可以使用this.$nextTick()(为了兼容性)
  3. SSR场景nextTick在服务端渲染中不会做任何事情

总结

  • DOM更新是异步的,数据变化后不能立即获取更新后的DOM
  • 使用nextTick()来确保在DOM更新后再执行操作
  • 这在处理动画、计算尺寸或位置等场景时特别重要

你知道为什么Vue要选择异步更新DOM吗?欢迎在评论区留言讨论!

我是大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!

📌往期精彩

《写给小公司前端的 UI 规范:别让页面丑得自己都看不下去》

《只会写 Mapper 就敢说会 MyBatis?面试官:原理都没懂》

《别再手写判空了!SpringBoot 自带的 20 个高效工具类》

《别学23种了!Java项目中最常用的6个设计模式,附案例》

《Vue3+TS设计模式:5个真实场景让你代码更优雅》

相关推荐
Cyann3 小时前
Day1- React基础组件使用
前端·react.js
GISer_Jing3 小时前
Next系统学习(二)
前端·javascript·node.js
BillKu4 小时前
vue3 中 npm install mammoth 与 npm install --save mammoth 的主要区别说明
前端·npm·node.js
Ankle4 小时前
vue3 父子组件v-model传值方法总结
前端·vue.js
Liquidliang4 小时前
用Claude Code构建AI创意工作流:连接nano banana与veo3
前端·aigc
半花4 小时前
【Vue】defineProps直接和withDefaults设置默认值区别
前端·vue.js
游九尘4 小时前
服务器都是用的iis, 前端部署后报跨域,不是用同一个服务器 是前端项目的服务器做Nginx转发,还是后端项目的服务器做Nginx转发?
服务器·前端·nginx
携欢4 小时前
PortSwigger靶场之DOM XSS in jQuery selector sink using a hashchange event通关秘籍
前端·jquery·xss
Apifox4 小时前
如何让 Apifox 发布的在线文档具备更好的调试体验?
前端·后端·测试