前言
我们在 Web 开发中,最为关注的就是实时性和响应性,这也是用户体验的关键。Vue.js 是一款流行的 JavaScript 框架,以其优雅的数据绑定和响应式更新而闻名。然而,在编写复杂的应用程序时,我们经常需要处理异步 DOM 更新的情况,这可能导致一些意外行为或者需要特殊处理。这时候,Vue 提供了一个强大的工具来解决这个问题:nextTick
方法。
什么是nextTick()?
nextTick()
是 Vue 的核心方法之一,官方文档解释如下:
在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM。
根据这个解释,我们可以知道放在nextTick()
中的回调函数应该是会对 DOM 进行操作的代码。我们先来个简单的场景理解一下:
xml
<template>
<div>
<p ref="refP">消息:{{ message }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const message = ref('第一条消息')
const refP = ref(null)
console.log(refP.value)
</script>
这个场景中,会打印出null
,这是因为在 Vue 的生命周期中,setup
是最先执行的,这时组件还没渲染完成,refP.value
并不能读取到 p 标签,所以打印的是null
。如果要拿到我们想要的结果该怎么做呢?
scss
nextTick(() => {
console.log(refP.value)
})
我们只需将console.log(refP.value)
放入nextTick()
的回调函数中即可,这时打印为 <p>消息:初始消息</p>
所以,你可以把 nextTick()
理解为一个能够让你在 Vue 数据更新并且对应的 DOM 渲染完成后执行的延迟函数。可以让你在 DOM 更新完成后立即执行一些操作,这些操作可能依赖于更新后的 DOM 结构。
我们什么时候需要使用到nextTick()呢?
有一个这样的场景:你在项目中需要在修改了 DOM 元素的数据后,立即对新的 DOM 执行一系列 JS 操作时,这时就需要使用 nextTick()
方法。 通俗的理解是:更改数据后当你想立即使用 JS 操作新的视图的时候需要使用它,比如:
xml
<template>
<div>
<button @click="updateList">更新列表</button>
<ul>
<li v-for="item in list">{{item}}</li>
</ul>
</div>
</template>
<script setup>
import { ref } from 'vue';
const list = ref(new Array(20).fill(0))
const updateList = ()=> {
list.value.push(...new Array(10).fill(1))
const liItem = document.querySelector('li:last-child')
liItem.scrollIntoView({behavior:'smooth'})
}
</script>
<style lang="css" scoped>
li{
height: 100px;
margin: 10px;
background: rgb(67, 232, 67);
}
</style>
这个场景中,它包含一个按钮和一个无序列表。点击按钮会更新列表内容,并将新的元素添加到列表中。同时,代码会尝试将页面滚动到新添加的元素处,以确保用户能够看到新的列表项。当我们点击更新列表时,会发生什么呢?
我们会发现页面并没有划到最后一个1
那里,这是由于 Vue 的数据更新是异步的,导致在 DOM 更新之前就执行了滚动操作,从而无法确保滚动到最新添加的元素。
我们同样使用上nextTick
,效果就大大不同了。
xml
<script setup>
import { ref, nextTick } from 'vue';
// import {myNextTick} from '../components/nextTick'
const list = ref(new Array(20).fill(0))
const updateList = () => {
list.value.push(...(new Array(10).fill(1)))
nextTick(() => {
const liItem = document.querySelector('li:last-child')
liItem.scrollIntoView({ behavior: 'smooth' })
})
}
</script>
nextTick的使用原理
nextTick()
方法的使用原理涉及到 Vue.js 中的异步更新队列以及 JavaScript 的事件循环机制。
在 Vue.js 中,当你修改了数据后,Vue 会将 DOM 更新放入一个队列中,并在适当的时机异步执行这些更新。这样做的好处是可以将多个数据变更合并成一次 DOM 更新,去掉重复数据造成的不必要的计算以及 DOM 操作。但是,这也导致了一个问题:在数据更新后立即获取更新后的 DOM 或者对更新后的 DOM 进行操作时,可能会遇到更新尚未完成的情况。
这时候,nextTick()
方法就派上用场了,它的原理是利用 JavaScript 的事件循环机制,nextTick()
方法会在当前 JavaScript 执行栈执行完毕后立即执行传入的回调函数,但在下一个事件循环时执行。这样就能确保回调函数在 Vue 实例更新 DOM 后再执行,从而获取更新后的 DOM 或进行相应的操作。
手写nextTick🔥🔥🔥
在手写nextTick
时,我们要了解一下MutationObserver
, MutationObserver
是一个 JavaScript API,用于监视 DOM 树的变化。它提供了一种异步的方式来观察 DOM 节点的变化,并在节点被添加、删除、修改属性等操作时执行回调函数。
接下来就是nextTick
的手写代码了!!!
javascript
export function myNextTick(fn) {
let app = document.getElementById('app')
var observerOptions = {
childList: true, // 观察目标子节点的变化,是否有添加或者删除
attributes: true, // 观察属性变动
subtree: true, // 观察后代节点,默认为 false
};
// 让fn()在dom更新完成后执行
// 创建一个DOM监听器
let observer = new MutationObserver((el) => {
// 当被监听的DOM更新完成时,该回调会触发
console.log(el);
fn()
})
observer.observe(app, observerOptions) // 监听上某个dom节点及子节点
}
在这里我们定义了一个 observerOptions
对象,用于配置 MutationObserver
的选项,包括是否观察目标子节点的变化、是否观察属性变动以及是否观察后代节点的变化。然后创建了一个 MutationObserver
实例,并传入一个回调函数作为参数。当被监听的 DOM 更新完成时,该回调函数会被触发。其中调用 observer.observe(app, observerOptions)
方法就会开始监听目标节点的变化。
总之,myNextTick
函数通过 MutationObserver
监听 DOM 变化,实现了在 DOM 更新完成后执行回调函数的功能,从而达到类似于 nextTick()
的效果。
结尾 🌸🌸🌸
总结:
- 在DOM更新后执行回调 (起到了等待DOM渲染完成的作用)
- nextTick 是异步 微任务
- 页面渲染完成后执行
看完这篇文章,小伙伴们又掌握了 Vue 的一个知识点,面对面试官的问题也能灵活应对了,恭喜大家,祝你也祝我在今后日子里能够登高望远,心向彼岸。