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个真实场景让你代码更优雅》

相关推荐
苏打水com10 小时前
第十四篇:Day40-42 前端架构设计入门——从“功能实现”到“架构思维”(对标职场“大型项目架构”需求)
前端·架构
king王一帅10 小时前
流式渲染 Incremark、ant-design-x markdown、streammarkdown-vue 全流程方案对比
前端·javascript·人工智能
苏打水com11 小时前
第十八篇:Day52-54 前端跨端开发进阶——从“多端适配”到“跨端统一”(对标职场“全栈化”需求)
前端
Bigger11 小时前
后端拒写接口?前端硬核自救:纯前端实现静态资源下载全链路解析
前端·浏览器·vite
BD_Marathon11 小时前
【JavaWeb】路径问题_前端绝对路径问题
前端
whyfail12 小时前
Vue原理(暴力版)
前端·vue.js
Bigger12 小时前
Coco AI 技术演进:Shadcn UI + Tailwind CSS v4.0 深度迁移指南 (踩坑实录)
前端·css·weui
踢球的打工仔12 小时前
jquery的基本使用(3)
前端·javascript·jquery
前端无涯12 小时前
Trae的使用
前端·ide·trae