Skip to content

nextTick

Vue 3中的DOM更新是异步进行的,这意味着当你更改了某些数据时,视图不会立即更新。Vue会将这些更改放入一个队列中,在稍后的时刻一起执行,以提高效率。听起来就像把你的快递包裹塞到已经超满的卡车上,而Vue就是一个聪明的物流主管,它选择合适的时机发送出去。

示例

下面代码中通过一个文本框发送消息,它应该立即出现在聊天框中,并且视图要滚动到最新的消息。

在这个小案例中,就涉及到了数据更新和DOM更新。关键点来了,更新DOM时,我们常用的操作,比如element.scrollTop = newScrollTop,是同步进行的。但是DOM更新是异步操作,所以当更新滚动条位置时,可能新消息的DOM还没渲染出来。

这时候就可以用到nextTicknextTick就像是一个“等一下”的信号,它告诉Vue:"Hey,等你把这堆异步工作做完后,通知我一声,我还有点事要做呢!"。

代码如下:

vue
<template>
    <div class="boxs">
        <div ref="box" class="chats">
            <div v-for="message in messages" class="chat">
                <p>{{ message }}</p>
            </div>
        </div>
        <textarea v-model="sendM" class="send">

        </textarea>
        <button @click="send">send</button>
    </div>
</template>

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

const messages = ref<string[]>(['Rarrot:你好'])

const sendM = ref<string>()

const box = ref<HTMLDivElement>()

const send = async () => {
    if (sendM.value && sendM.value != '') {
        messages.value?.push('小明说:' + sendM.value)
        // sendM.value = ''
        
        // 1. 给nextTick传入一个回调函数
        // nextTick(()=>{
        //     box.value!.scrollTop = 12345678
        // })

        // 2. 将send函数变为异步函数,然后使用以下代码:
        // nextTick()返回一个Promise,
        // await关键字是用来等待一个异步操作完成的,在这里会等待异步DOM更新后再执行后面的代码,直到被等待的Promise被解决(或拒绝)。
        await nextTick()
        box.value!.scrollTop = 12345678
    }
}
</script>
<style lang="less" scoped>
.boxs {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    .chats {
        width: 450px;
        height: 450px;
        border: 1px solid #ccc;
        overflow: auto;
        .chat {
            padding: 8px;
            background-color: #c0bec0;
            margin-bottom: 1px;
            p {
                font-size: 18px;
            }
        }
    }
    .send {
        .chats();
        height: 80px;
        margin-top: 10px;
    }
    button {
        margin-left: 350px;
        width: 100px;
    }
}
</style>

nextTick让我们能够在Vue完成DOM更新后执行某些操作,解决数据更新和DOM更新的时间差问题。

源码解读

在源码中,nextTick()是将传入的函数放到Promise中,然后执行一个微任务:

ts
export function nextTick<T = void, R = void>(
  this: T,
  fn?: (this: T) => R
): Promise<Awaited<R>> {
  const p = currentFlushPromise || resolvedPromise

  // 1. 若函数 fn 存在:
  // 1.1 若 this 存在(即 nextTick 函数被一个对象调用),则将 fn 绑定到 this 上,这样在 fn 中使用 this 时,它将引用调用 nextTick 的对象。
  // 1.2 若 this 不存在(即 nextTick 函数被直接调用),则不需要绑定 fn,直接使用原始的 fn。

  // 2. 若函数 fn 不存在:
  // 2.1 则直接返回 p 这个已解析的 Promise。 
  return fn ? p.then(this ? fn.bind(this) : fn) : p
}
ts
let currentFlushPromise: Promise<void> | null = null
ts
const resolvedPromise = /*#__PURE__*/ Promise.resolve() as Promise<any>

Released under the MIT License.