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

前言

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

详细MutationObserver介绍戳这里!!!

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

相关推荐
RobinDevNotes22 分钟前
从 Electron 到 GPUI,9 个跨平台桌面方案该怎么选
前端·javascript·rust·electron·openresty·前端开发工具
jay神1 小时前
基于 YOLOv8 的轴承缺陷检测(Web 版)
前端·人工智能·yolo
jay神1 小时前
基于 YOLOv8 的森林火灾检测系统(Web 版)
前端·yolo
code2cat2 小时前
HTML基础篇之第一张网页:从index.html到浏览器里的任务清单
前端·html
miss2 小时前
AI 改完不敢上线?OpenSpec 规约驱动开发实战
前端·代码规范·前端工程化
AiMan3 小时前
腾讯ima还是不可以导出左侧所有对话,只能单条导出,方法不对吗
前端
去伪存真3 小时前
并发量一高就装死?Agent 全链路“防猝死”手册
前端·agent
Moment3 小时前
AI 编程时代,聊聊我的 Mac mini 双屏桌面
前端·后端
AiMan3 小时前
先确认能不能一次把Microsofat Copilot所有内容打包一个文件导出
前端
时间的拾荒人3 小时前
Redis 入门详解:核心特性、重要文件与全局命令实战
开发语言·redis·面试