【Vue3】30-nextTick

262 阅读1分钟

1. 作用与用法

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

nextTick() 可以在状态改变后立即使用,以等待 DOM 更新完成。你可以传递一个回调函数作为参数,或者 await 返回的 Promise。

<script setup>
import { ref, nextTick } 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>

那么浏览器在一个 “tick” 里做了什么?

1. 处理用户的事件,就是 event,例如 click,input,change等
2. 执行定时器任务
3. 执行 requestAnimationFrame
4. 执行 DOM 的回流与重绘
5. 计算更新图层的绘制指令
6. 绘制指令合并主线程,如果有空余时间会执行 requestidlecallback

2. 案例

聊天框的滚动条在发送消息后自动回底
<template>
<div class="wrap">
    <div class="chat" ref="chatBox">
        <p v-for="msg in chatList">{{ msg }}</p>
    </div>
    <div class="send">
        <textarea type="text" v-model="myMsg" rows="5"/>
        <button @click="send">发送</button>
    </div>
</div>
</template>

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

let chatList = reactive(["我是 lzy"])
let myMsg = ref("")
let chatBox = ref()
const send = async () => {
    if(myMsg.value === "") return alert("请输入内容!")
    chatList.push(myMsg.value)

    // 调用 nextTick 来传递回调,等 DOM 更新后再执行回调里面的代码
    nextTick(() => {
        chatBox.value.scrollTop = 999
    })

    // 或者这样写
    // await nextTick()
    // chatBox.value.scrollTop = 999
}
</script>

<style scoped>
.wrap {
    border: 1px solid #ccc;
    width: fit-content;
}

.chat{
    width: 400px;
    height: 500px;
    box-sizing: border-box;
    padding: 20px;
    overflow: auto;
}
.chat p {
    border: 1px solid skyblue;
    width: fit-content;
    padding: 10px;
    border-radius: 5px;
    max-width: 300px;
    word-wrap: break-word;
}

.send{
    border: 1px solid #ccc;
    width: 400px;
    height: 200px;
    position: relative;
}
.send textarea {
    border: none;
    outline: none;
    display: block;
    width: 400px;
    box-sizing: border-box;
    padding: 10px;
    font-size: 20px;
    resize: none;
}
.send button {
    position: absolute;
    bottom: 10px;
    right: 10px;
    outline: none;
    border: 1px solid #ccc;
    padding: 10px 30px;
    border-radius: 5px;
    cursor: pointer;
}
</style>

可以这样理解,如果先设置了滚动条的 scrollTop,那么 DOM 更新后,滚动条就可能变短(因为消息多了,而聊天框的总高度一定,所以滚动条长度变短),而 scrollTop 没有再次设置,这时候页面呈现的效果就是滚动条没有在底部。

使用 nextTick 之后,我们先等页面更新,此时聊天框的高度已经确定了,再执行 nextTick 的回调来设置滚动条的 scrollTop,这时候页面呈现的效果就是滚动条到达底部。