Skip to content
On this page

nextTick

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

0
<template>
  <div ref="countRef">{{ count }}</div>
  <button @click="increase">increase</button>
</template>

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

const countRef = ref()
const count = ref(0)

const increase = () => {
  count.value++

  // console.log('instance: ', countRef.value.innerText)

  for (let i = 0; i < 1000; i++) {
    count.value = i
  }
}

onUpdated(() => {
  console.log('update')
})
</script>

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

Vue 在更新 DOM 时是异步执行的。DOM 节点更新成功后调用

import { createApp, nextTick } from 'vue'

const app = createApp({
  setup() {
    const message = ref('Hello!')
    const changeMessage = async newMessage => {
      message.value = newMessage
      await nextTick()
      console.log('Now DOM is updated')
    }
  }
})

实现原理

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
}

异步渲染

获取异步渲染后的宽高

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

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

type User = {
  email: string
}

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

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

  loading.value = false

  return response.data.results
}

const onFetchUserList = async () => {
  userList.value = await fetchUser()

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