了解Vue.js中的$nextTick

762 阅读2分钟

一个前端开发者(假设他的名字是Eric)走进了一家Vue酒吧。

Eric点了他最喜欢的鸡尾酒:Nuxt。调酒师正在调酒。然后他开始咆哮。

他开始说他是如何在Vue 3文档中的实例方法下发现$nextTick ,并被震撼了。Eric使用Vue已经有一段时间了,他习惯于把$watch$emit 写成实例方法。那么,$nextTick 有什么用呢?Vue文档说,它"[推迟]回调,在下一个DOM更新周期后执行"。

但Eric并不相信。

他继续叙述了他是如何尝试这样做的。

this.loadingAnimation = true
this.startVeryLongCalculation()
this.completeVeryLongCalculation()
this.loadingAnimation = false

但浏览器什么也没显示。他去了StackOverflow,有人推荐使用$nextTick 。这个人说,"它推迟了回调",但至少他们包括一个代码片段。埃里克把他的代码修改成这样。

this.loadingAnimation = true
this.$nextTick(() => this.startVeryLongCalculation())
this.endVeryLongCalculation()
this.loadingAnimation = false

它成功了。为什么?

在这篇文章中,如果你和Eric有类似的情况,你就会明白nextTick 是如何工作的,并看到用一个真实的用例证明。

[H2] 先决条件

你应该有以下的基本知识。如果你不知道,请回顾这些主题。

  • 事件循环
  • 回调函数
  • 微任务、队列和时间表
  • 异步更新队列
    [H2]nextTick 是做什么的?

nextTick 接受一个回调函数,它被延迟到下一个DOM更新周期。它只是Vue的一种说法:"嘿,如果你想在DOM更新后执行一个函数(这种情况很少发生),我希望你使用nextTick 而不是setTimeout"。

Vue.nextTick(() => {}) // syntax

我们很快就会讨论setTimeoutnextTick 的争论。让我们通过这个例子来直观地了解nextTick 的行为。

<template>
  <div>
    {{ currentTime }}
  </div>
</template>

<script>
export default {
  name: 'getCurrentTime',
  data() {
    return {
      currentTime: ''
    }
  },
  mounted() {
    this.currentTime = 3;

    this.$nextTick(() => {
        let date = new Date()
        this.currentTime = date.getFullYear()
    });
  }
}
</script>

注意:this.$nextTick 与全局API方法vue.nextTick 相同,只是回调函数的this 被自动绑定到调用它的实例上。

在JSFiddle或你的电脑上运行这个代码片段。它将显示2021 。并不是说如果你删除nextTick ,就不会得到同样的结果。然而,你应该明白,Vue是根据数据中的内容对DOM进行修改的。

在上面的代码片段中,Vue将DOM更新为3 ,然后调用回调,将DOM更新为2021 ,最后将控制权交给浏览器,显示2021

到目前为止,我们已经探讨了nextTick ,在回调队列中插入回调函数,并在合适的时候执行该函数的部分。

这就是你需要知道的一切。

但你会有兴趣知道,nextTick 中的回调是作为事件循环中的一个微任务使用的。nextTick的源代码明确指出:"nextTick行为利用了微任务队列,它可以通过本地Promise.thenMutationObserver 。"

[H3]se``tTimeout 对比。nextTick

在DOM被更新后执行一个函数的另一种方式是使用JavaScriptsetTimeout() 函数。

让我们在上面同样的代码例子中用setTimeout 替换nextTick

<template>
  <div>
    {{ currentTime }}
  </div>
</template>

<script>
export default {
  name: 'getCurrentTime',
  data() {
    return {
      currentTime: ''
    }
  },
  mounted() {
    this.currentTime = 3;

    setTimeout(() => {
      let date = new Date()
      this.currentTime = date.getFullYear()
    }, 0);
  }
}
</script>

在你的本地服务器或这个JSFiddle上运行这个代码片段。你会先看到3 ,然后是2021 。它发生得很快,所以如果你一开始没有看到这种行为,你可能需要刷新浏览器。

在上面的代码片段中,Vue将DOM更新为3 ,并给浏览器控制权。然后浏览器显示3 ,调用回调,将DOM更新为2021 ,最后将控制权交给浏览器,现在浏览器显示2021

nextTick 的实现将setTimeout 作为最后的备用方法,用于无法使用PromiseMutationObserver 的浏览器(IE 6-10 和 Opera Mini 浏览器)。对于不支持PromiseMutationObserver 的浏览器(IE 10),它甚至更倾向于使用setImmediate

只有Opera Mini浏览器不具备这三种方法,而不得不使用setTimeout

[H2] 何时使用nextTick

很少有情况需要你拿出大的nextTick 。其中一些情况是。

  • 当您想使用setTimeout
  • 当你想非常确定DOM能反映你的数据时
  • 当你在尝试执行异步操作时遇到错误,如Uncaught (in promise) DOMException 。记住,Vue是异步更新DOM的

让我们通过一个使用Vue 3的最后例子。

<div id="app">
  <div ref="listScroll" class="scrolledList">
    <ul ref="scrolledHeight">
      <li v-for="month in months">
        {{month}}
      </li>               
    </ul>
  </div>

  <input type="text" placeholder="Add Month" v-model="month">
  <button @click="addMessage" @keyup.enter="addMessage"> Add Month</button>
</div>

<script src="https://unpkg.com/vue@next"> 
  Vue.createApp({
    data() {
      return {
        month: '',
        months: ['Jan', 'Feb', 'Apr', 'May', 'June', 'July', 'Aug']
      }
    },
    mounted() {
      this.updateScrollNextTick()
    },
    methods: {
      addMessage() {
        if(this.month == ''){
          return
        }

        this.months.push(this.month)
        this.month = ''
        this.updateScrollNextTick()
      },
      updateScrollNextTick () {
        let scrolledHeight = this.$refs.scrolledHeight.clientHeight

        this.$nextTick(() => {
          this.$refs.listScroll.scrollTo({
            behavior: 'smooth',
            top: scrolledHeight
          })
        })
      }
    },
  })
  .mount("#app")
</script>

在你的本地机器或CodePen上运行这个。你应该得到像这样的东西。

nextTick illustration

在上面的代码片段中,我们想在一个新项目被添加到列表中时获得平滑的向下滚动效果。浏览一下代码,尝试修改一下,删除nextTick ,你会失去那种平滑的滚动效果。你也可以尝试用setTimeout 来代替nextTick

[H2] 结语

在这篇文章中,我们已经探讨了nextTick 是如何工作的。我们进一步了解了它与vanilla JavascriptsetTimeout 的不同之处,并涵盖了实际的使用案例。

前面提到的实例方法很少需要,所以如果你最近不得不使用它,请在这篇文章下留言,分享你的经验。

The postUnderstanding $nextTick in Vue.js appearedfirst on LogRocketBlog.