VUE:nextTick在DOM 更新循环结束之后执行

是什么

nextTick 是 Vue 提供的一个全局 API,它接收一个回调函数,并确保这个回调在下一次 DOM 更新循环结束之后执行。简单来说,它让你能够“等待” Vue 完成对 DOM 的更新,然后执行你的代码。

在 Vue 3 中,可以通过 import { nextTick } from 'vue' 导入使用。

为什么需要 nextTick

Vue 采用异步更新队列的策略来提升性能。当你修改响应式数据时,Vue 并不会立即更新 DOM,而是将这次更新操作推入一个队列中。如果在同一个“事件循环 tick”中多次修改数据,这些修改会被合并,最终只触发一次 DOM 更新。这样做可以避免不必要的计算和渲染,提高性能。

但由于 DOM 更新是异步的,如果你在修改数据后立即尝试读取 DOM 的状态(比如元素的高度、文本内容等),你得到的可能还是更新前的旧值。nextTick 就是用来解决这个问题的——它让你可以在 DOM 更新完成后执行回调,从而获取到最新的 DOM 状态。

使用 nextTick 解决上面问题

<template>
  <view>
    <view id="counter">{{ count }}</view>
    <button @click="showDOM">点我</button>
  </view>
</template>

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

const count = ref(0)

const showDOM = async () => {
  count.value = 5  // 修改数据
  
  // ❌ 错误:立即获取
  console.log('立即获取:', document.getElementById('counter').textContent)  // 0
  
  // ✅ 正确:等待DOM更新后再获取
  nextTick(()=>{
  	console.log('nextTick后:', document.getElementById('counter').textContent)  // 5	
  })
  console.log('这个时候nextTick还么执行:', document.getElementById('counter').textContent)
}
</script>

日志如下:

19:24:24.231 立即获取: 0 at pages/nextTick/nextTick.vue:17
19:24:24.239 这个时候nextTick还么执行: 0 at pages/nextTick/nextTick.vue:23
19:24:24.239 nextTick后: 5 at pages/nextTick/nextTick.vue:21

nextTick 的工作原理

// 简化版理解
const add = () => {
  count.value = 5  // 1. 修改数据
  
  // 2. Vue把DOM更新任务放到队列
  // queue: [更新count的DOM]
  
  // 3. 同步代码继续执行
  console.log('数据已改,但DOM未更新')  // 立即执行
  
  nextTick(() => {
    // 4. 等同步代码执行完,DOM更新完成后
    // 这里的代码才执行
    console.log('DOM已更新,可以安全操作')
  })
  
  // 5. 同步代码结束
}

// 执行顺序:
// 1. 修改数据 count.value = 5
// 2. 执行 console.log('数据已改,但DOM未更新')
// 3. 注册 nextTick 回调
// 4. 同步代码结束
// 5. Vue开始更新DOM (0变成5)
// 6. 执行 nextTick 回调

基本语法形式

回调函数形式

import { nextTick } from 'vue'

// 基本用法
nextTick(() => {
  // DOM更新后执行的代码
  console.log('DOM已更新')
})
// 这里的代码会立即执行,不会等待

// 带参数的用法
nextTick((arg1, arg2) => {
  console.log('参数:', arg1, arg2)
}, '参数1', '参数2')  // 额外的参数会传给回调函数
// 这里的代码会立即执行,不会等待

Promise 形式(推荐)

import { nextTick } from 'vue'

// 使用 .then()
nextTick().then(() => {
  // 这里的代码会在DOM更新后执行
  console.log('DOM已更新')
})
// 这里的代码会立即执行,不会等待



// 使用 async/await(最清晰)
const handleClick = async () => {
  count.value++
  await nextTick()  // 等待DOM更新
  // 这里的代码会在DOM更新后执行
  console.log('DOM已更新,可以安全操作')
  // 后面的代码也会等待
}

nextTick 执行时机

场景 nextTick 执行时机 执行次数
组件创建时注册 首次DOM渲染完成后 1次
点击事件中注册 本次点击导致的DOM更新完成后 每次点击1次
没有DOM更新 永远不会执行 0次
多次数据变化 所有变化合并后的那次DOM更新 1次
多次注册 按注册顺序执行 对应那次DOM更新

组件创建时的 nextTick

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

console.log('1. 组件开始创建')

// 这个 nextTick 只会在首次DOM更新后执行一次
nextTick(() => {
    console.log('3. 首次DOM更新完成')
})

console.log('2. 组件创建完成')
</script>

<template>
    <view>模板内容</view>
</template>

// 输出顺序:
// 1. 组件开始创建
// 2. 组件创建完成
// (模板渲染)
// 3. 首次DOM更新完成   ← 只执行一次

事件处理中的 nextTick

<template>
    <view>
        <view id="counter">{{ count }}</view>
        <button @click="handleClick">点击</button>
    </view>
</template>

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

const count = ref(0)

const handleClick = () => {
    count.value++  // 修改数据
    
    // 这个 nextTick 会在本次点击导致的DOM更新后执行
    nextTick(() => {
        console.log('DOM已更新,新值为:', document.getElementById('counter').textContent)
    })
}

// 点击第一次输出:
// DOM已更新,新值为: 1

// 点击第二次输出:  
// DOM已更新,新值为: 2

// 每次点击都会执行这个 nextTick
</script>

多个 nextTick 的执行顺序

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

const count = ref(0)

// nextTick 1
nextTick(() => {
    console.log('nextTick 1')
})

// nextTick 2  
nextTick(() => {
    console.log('nextTick 2')
})

// nextTick 3
nextTick(() => {
    console.log('nextTick 3')
})

const handleClick = () => {
    count.value++
    
    // nextTick 4
    nextTick(() => {
        console.log('nextTick 4')
    })
    
    // nextTick 5
    nextTick(() => {
        console.log('nextTick 5')
    })
}

// 页面加载后输出:
// nextTick 1
// nextTick 2
// nextTick 3

// 点击按钮后输出:
// nextTick 4
// nextTick 5
</script>

×

喜欢就点赞,疼爱就打赏