likes
comments
collection
share

细聊Vue中的nextTick(面试常考!!!附带手写nextTick代码)

作者站长头像
站长
· 阅读数 8

前言

我们在 Web 开发中,最为关注的就是实时性和响应性,这也是用户体验的关键。Vue.js 是一款流行的 JavaScript 框架,以其优雅的数据绑定和响应式更新而闻名。然而,在编写复杂的应用程序时,我们经常需要处理异步 DOM 更新的情况,这可能导致一些意外行为或者需要特殊处理。这时候,Vue 提供了一个强大的工具来解决这个问题:nextTick 方法。

什么是nextTick()?

nextTick()是 Vue 的核心方法之一,官方文档解释如下:

在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM。

根据这个解释,我们可以知道放在nextTick()中的回调函数应该是会对 DOM 进行操作的代码。我们先来个简单的场景理解一下:

<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。如果要拿到我们想要的结果该怎么做呢?

nextTick(() => {
  console.log(refP.value)
})

我们只需将console.log(refP.value)放入nextTick()的回调函数中即可,这时打印为 <p>消息:初始消息</p>

所以,你可以把 nextTick() 理解为一个能够让你在 Vue 数据更新并且对应的 DOM 渲染完成后执行的延迟函数。可以让你在 DOM 更新完成后立即执行一些操作,这些操作可能依赖于更新后的 DOM 结构。

我们什么时候需要使用到nextTick()呢?

有一个这样的场景:你在项目中需要在修改了 DOM 元素的数据后,立即对新的 DOM 执行一系列 JS 操作时,这时就需要使用 nextTick() 方法。 通俗的理解是:更改数据后当你想立即使用 JS 操作新的视图的时候需要使用它,比如:

<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>

这个场景中,它包含一个按钮和一个无序列表。点击按钮会更新列表内容,并将新的元素添加到列表中。同时,代码会尝试将页面滚动到新添加的元素处,以确保用户能够看到新的列表项。当我们点击更新列表时,会发生什么呢?

细聊Vue中的nextTick(面试常考!!!附带手写nextTick代码)

我们会发现页面并没有划到最后一个1那里,这是由于 Vue 的数据更新是异步的,导致在 DOM 更新之前就执行了滚动操作,从而无法确保滚动到最新添加的元素。

我们同样使用上nextTick,效果就大大不同了。

<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>

细聊Vue中的nextTick(面试常考!!!附带手写nextTick代码)

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 节点的变化,并在节点被添加、删除、修改属性等操作时执行回调函数。

详细MutationObserver介绍戳这里!!!

接下来就是nextTick的手写代码了!!!

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 的一个知识点,面对面试官的问题也能灵活应对了,恭喜大家,祝你也祝我在今后日子里能够登高望远,心向彼岸。