• 作者:老汪软件技巧
  • 发表时间:2024-09-17 04:01
  • 浏览量:

前言

在vue中,nextTick是一个非常有用的方法,可以帮助我们解决一些异步更新队列相关的问题.由于vue中的数据响应系统是基于异步更新机制的,当我们修改完数据后,视图不会立即更新,而是会等待下一个DOM更新周期才会开始渲染.为此我们需要nextTick帮助我们等待下一次 DOM 更新.

正文

场景实例

html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Documenttitle>
    <style>
        h2{
            display: inline-block;
        }
    style>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js">script>
head>
<body>
<div id="app">
    <h2 ref="h2Ref">{{ message }}h2>
    <button @click="updateMessage">更新button>
div>
<script>
  const { createApp, ref ,onMounted} = Vue
  createApp({
    setup() {
      const message = ref('Hello vue!')
      const h2Ref = ref(null)
       onMounted(()=>{
         console.log(h2Ref.value.clientWidth);
       })
      const updateMessage = ()=>{
        message.value = 'Goodbye Vue!'
            console.log(h2Ref.value.clientWidth);
      }
       return {
        message,
        h2Ref,
        updateMessage
      }
    }
  }).mount('#app')
script>
body>
html>

在上述代码中我们在页面挂载完毕时打印了message的宽度,获取宽度需要之一的一个常见问题就是区别clientWidth和offsetWidth,前者是不会计算边框的宽度,后者是会计算边框的宽度的.所以这里我们使用的是clinetWidth.这里我们的流程是点击button按钮,然后message的信息会更新然后再打印出更新后的message的宽度.我们需要思考的一个问题,这样子真的能打印出更新后的宽度吗?

答案是不能的.点击第一次更新时获取到的宽度依旧是未更新之前的宽度?那么我们思考一下,当我们点击更新按钮,代码的执行顺序是什么,DOM结构中的h2是什么时候渲染的第一时间是先执行的是message.value = 'Goodbye Vue!',第二时间执行的是console.log(h2Ref.value.clientWidth);最后才是修改DOM结构中的的message.这样我们第一次点击更新,打印的内容依旧是未更新之前的宽度.那么如果我们让这个打印的顺序在最后呢?让messageDOJM结构先行.此时我们就需要使用定时器来等待执行了。

const updateMessage = ()=>{
        message.value = 'Goodbye Vue!'
        setTimeout(()=>{
            console.log(h2Ref.value.clientWidth);
        })
      }

将打印添加到定时器中执行.打印的结果如何呢?

打印结果是更新之后的宽度,这也就是说明了使用定时器是可行的.但是问题就是使用定时器需要的等待时间,而且等待的时间不是精确的,如果项目比较大,这就会导致需要等待的时间也会比较长.所以这终究不是一个合适的的方法.

那么我们使用nextTick可以很好的解决这种问题.

const updateMessage = ()=>{
        message.value = 'Goodbye Vue!'
        nextTick(()=>{
            console.log(h2Ref.value.clientWidth)
        })
      }

nextTick它会保证内部代码会在页面渲染完成之后执行,也就可以理解为某些需要等待DOM结构更新完毕的操作放在nextTick.

我们看vue的官方文档中,有提到nectTick会返回一个Promise的对象.也就是说nextTick的返回值可以接.then()。我们看看nextTick的返回值是什么

vue实现路由跳转原理_$nexttick原理_

const updateMessage = ()=>{
        message.value = 'Goodbye Vue!'
        let res = nextTick(()=>{
            console.log(h2Ref.value.clientWidth)
        })
        res.then(()=>{
            console.log(res);
            
        })
      }

返回值是一个Promise对象且状态是fullfilled也就是说在vue源码执行中调用了resolve().

实现nextTick

我们创建一个js文件,我们知道的nextTick会返回一个Promise对象,且nextTick接受一个回调函数.那么我们就可以先把模版写好.

function nextTick(fn) {
    return new Promise((resolve, reject) => {
      
    })
  }

然后我们就需要梳理一下我们需要干什么了?首先我们必须检查DOM结构是否发生了更新,如果更新了就需要调用nextTick的回调函数以及resolve().

我们如何知道DOM结构发生了更新呢?那么这个时候就需要一个辅助API了MutationObserver

MutationObserver可以帮助监听DOM结构.使用这个我们上首先需要判断浏览器是否支持.

if (typeof MutationObserver !== 'undefined') {//判断浏览器支不支持
        const observer = new MutationObserver(() => {
          let res = fn()
          if (res instanceof Promise) {
            res.then(resolve)
          } else {
            resolve()
          }
         
        })
        observer.observe(document.getElementById('app'),{
            childList: true, // 观察目标子节点的变化,是否有添加或者删除
            attributes: true, // 观察属性变动
            subtree: true, // 观察后代节点,默认为 false
        })
      }

我们将回调函数fn赋值给res判断返回值是否一个Promise对象如果是则在fn执行完毕后调用resolve()否则直接调用resolve().

observer.observe()第一个参数是指我们需要监听的DOM节点,第二个对象参数是一些配置.

我们将该代码引入实例中是否也能实现效果呢?

html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Documenttitle>
    <style>
        h2{
            display: inline-block;
        }
    style>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js">script>
head>
<body>
<div id="app">
    <h2 ref="h2Ref">{{ message }}h2>
    <button @click="updateMessage">更新button>
div>
<script src="nextTick.js">script>
<script>
  const { createApp, ref ,onMounted} = Vue
  createApp({
    setup() {
      const message = ref('Hello vue!')
      const h2Ref = ref(null)
      onMounted(()=>{
        console.log(h2Ref.value.clientWidth);
      })
  
      const updateMessage = ()=>{
        message.value = 'Goodbye Vue!'
    
        let res = nextTick(()=>{
            console.log(h2Ref.value.clientWidth);
        })
        res.then(()=>{
            console.log(res);
            console.log('nextTick 执行完毕');
        })
      }
      
      return {
        message,
        h2Ref,
        updateMessage
      }
    }
  }).mount('#app')
script>
body>
html>

打印的结果是没有问题的,也就是我们确实是实现了nextTick这个方法.那么本文到此就结束了,希望对大家有所帮助.感谢大家阅读!!!