Skip to content

nextTick

当你在 Vue 中更改响应式状态时,最终的 DOM 更新并不是同步生效的,而是由 Vue 将它们缓存在一个队列中,直到下一个“tick”才一起执行。这样是为了确保每个组件无论发生多少状态改变,都仅执行一次更新。

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

const count = ref(0)

async function increment() {
  count.value++

  // DOM 还未更新
  console.log(document.getElementById('counter').textContent) // 0

  await nextTick()
  // DOM 此时已经更新
  console.log(document.getElementById('counter').textContent) // 1
}
</script>

<template>
  <button id="counter" @click="increment">
    {{ count }}
  </button>
</template>

将回调推迟到下一个 DOM 更新周期之后执行。在更改了一些数据以等待 DOM 更新后,浏览器重新渲染之前立即使用它。

实现原理

ts
export function nextTick<T = void>(
  this: T,
  fn?: (this: T) => void,
): Promise<void> {
  const p = currentFlushPromise || resolvedPromise
  return fn ? p.then(this ? fn.bind(this) : fn) : p
}

异步渲染

获取异步渲染后的宽高

vue
<script lang="ts" setup>
import axios from 'axios'
import { nextTick, ref } from 'vue'

interface User {
  email: string
}

const userList = ref<User[]>([])
const listRef = ref()
const loading = ref(false)

async function fetchUser(params = { results: 10 }) {
  loading.value = true
  const response = await axios('https://randomuser.me/api', {
    method: 'get',
    params,
  })

  loading.value = false

  return response.data.results
}

async function onFetchUserList() {
  userList.value = await fetchUser()

  console.log('nextTick: before', listRef.value.clientHeight)
  await nextTick()
  console.log('nextTick: after', listRef.value.clientHeight)
}
</script>

<template>
  <n-button :loading="loading" @click="onFetchUserList">
    获取用户列表
  </n-button>
  <ul ref="listRef">
    <li v-for="user in userList" :key="user.email">
      {{ user.email }}
    </li>
  </ul>
</template>