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

相关推荐
四月_h8 分钟前
vue2动态实现多Y轴echarts图表,及节点点击事件
前端·javascript·vue.js·echarts
文心快码BaiduComate31 分钟前
用Zulu轻松搭建国庆旅行4行诗网站
前端·javascript·后端
正义的大古1 小时前
OpenLayers地图交互 -- 章节十八:拖拽旋转和缩放交互详解
javascript·vue.js·openlayers
行者..................2 小时前
手动编译 OpenCV 4.1.0 源码,生成 ARM64 动态库 (.so),然后在 Petalinux 中打包使用。
前端·webpack·node.js
小爱同学_2 小时前
一次面试让我重新认识了 Cursor
前端·面试·程序员
golang学习记2 小时前
AI 乱写代码?不是模型不行,而是你的 VS Code 缺了 Context!MCP 才是破局关键
前端
星光不问赶路人3 小时前
Vite 中的 import.meta.glob vs 动态导入:该用哪个?
前端·vite
z_y_j2299704383 小时前
服务器中使用Docker部署前端项目
服务器·前端·docker·容器
迪丽热爱3 小时前
解决【npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚本。】问题
前端·npm·node.js
数字冰雹4 小时前
图观 流渲染场景服务器
服务器·前端·数据库·数据可视化