使用 Vue 已经有一段时间了,已经习惯了把$watch
和$emit
写成实例方法。那么,$nextTick
是用来做什么的?Vue 文档说,它”[defers]
回调,在下一个 DOM 更新周期后执行”。
但是我并不相信。
接下来看看我是如何尝试这样做的:
this.loadingAnimation = true this.startVeryLongCalculation() this.completeVeryLongCalculation() this.loadingAnimation = false
有用。 为什么?
nextTick 做什么?
nextTick
接受一个延迟到下一个 DOM 更新周期的回调函数。这只是 Vue 的一种说法,”嘿,如果你想在 DOM 更新后执行一个函数(这种情况很少发生),我希望你使用nextTick
而不是setTimeout
“。
Vue.nextTick(() => {}) // syntax
下面很快就会讲到setTimeout
和nextTick
参数。我们用这个例子来可视化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>
在 J 电脑上运行这个代码片段。它将显示2021
年。并不是说如果你去掉nextTick
,就不会得到同样的结果。然而,你应该明白,Vue 会根据数据中的内容对 DOM 进行修改。
在上面的代码片段中,Vue 将 DOM 更新为3
,然后调用回调,将 DOM 更新为2021
,最后将控制权交给浏览器,浏览器将显示2021
。
到目前为止,我们已经研究了 nextTick 在回调队列中插入回调函数并在适当的时候执行该函数。
这个你可能会感兴趣,nextTick
中的回调是作为事件循环中的一个微任务使用的。nextTick
的源代码明确指出,”nextTick
行为利用了微任务队列,可以通过本地的Promise.then
或MutationObserver
来访问。”
setTimeout vs nextTick
在 DOM 更新后执行函数的另一种方法是使用 JavaScript 的setTimeout()
函数。
我们将上面的代码用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>
运行此代码片段。 首先看到3
然后2021
。它发生得很快,因此如果没有看到此行为,需要刷新浏览器。
在上面的代码片段中,Vue 将 DOM 更新为3
,并提供浏览器控制。然后浏览器显示3
,调用回调函数,将 DOM 更新到2021
,最后将控制权交给浏览器,现在浏览器显示2021
。
nextTick
的实现在不支持Promise
和MutationObserver
的浏览器(IE 6-10 和 Opera Mini 浏览器)上,使用 setTimeout
作为后备方法,对于不支持Promise
和MutationObserver
的浏览器(IE 10),它更倾向于setImmediate
。
何时使用 nexttick
- 当你想使用
setTimeout
时 - 当你想确定 DOM 能反映你的数据时
- 在尝试执行异步操作时,遇到
Uncaught (in promise) DOMException
等错误。记住,Vue 是异步更新 DOM 的
最后来个示例:
<div id="app"> <div ref="listScroll" > <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>
示例地址:点击这里
主要部分:
在上面的代码片断中,我们想在一个新项目被添加到列表中时获得平滑的向下滚动效果。浏览一下代码,尝试修改一下,去掉$nextTick
,你就会失去那种平滑的滚动效果。你也可以尝试用setTimeout
来代替nextTick
。
总结
在本文中,我们探索了$nextTick
是如何工作的。我们进一步了解了它与普通的setTimeout
的不同之处,并介绍了实际的用例。