浅谈Vue种的$nextTick机制
nextTick出现的前提
因为在Vue种,其数据的变化和页面的渲染是异步的,即是我们在事件种修改了数据时,视图并不会立即更新,而是等在同一个事件循环(这里就涉及到了事件的循环机制)的所有数据都变化后,再执行视图的更新.
官方介绍
Vue.nextTick([callback,context])
-
参数
- {Function} [callback]
- {Object} [contect]
-
用法
在下次DOM更新循环结束之后执行回调,在修改数据之后立即使用这个方法,获取更新后的DOM
// 修改数据
vm.msg = 'Hello'
// 当我们在这里调用DOM的数据时,它其实还没有更新
Vue.nextTick(function () {
// DOM 更新了
})
// 2.1.0新增 Promise用法
Vue.nextTick()
.then(function () {
// 此时DOM已经更新
})
2.1.0 起新增:如果没有提供回调且在支持 Promise 的环境中,则返回一个 Promise。请注意 Vue 不自带 Promise 的 polyfill,所以如果你的目标浏览器不原生支持 Promise (IE:你们都看我干嘛),你得自己提供 polyfill
DOM循环更新
首先,Vue实例实现响应式并不是在数据改变之后就立即更新DOM,而是在一次循环的所有数据变化后再异步执行DOM更新
在这里简单总结一下事件循环
同步代码执行=>查找异步队列,进入执行栈,执行Callback1[事件循环1]=>查找异步队列,进入执行栈,执行Callback[事件循环2]=>…
即每一次异步的Callback都会再独立形成一次事件循环
因此我们可以推出nextTick的触发机制
一次循环种的所有代码执行完毕 = > DOM更新 = > 触发nextTick的回调Callback = > 进入下一次循环
下面来个例子,帮助更好的理解nextTick机制
<template>
<div class="app">
<div ref="contentDiv">{{content}}</div>
<div>在nextTick执行前获取内容:{{content1}}</div>
<div>在nextTick执行之后获取内容:{{content2}}</div>
<div>在nextTick执行前获取内容:{{content3}}</div>
</div>
</template>
<script>
export default {
name:'App',
data: {
content: 'Before NextTick',
content1: '',
content2: '',
content3: ''
},
methods: {
changeContent () {
this.content = 'After NextTick' // 在此处更新content的数据
this.content1 = this.$refs.contentDiv.innerHTML //获取DOM中的数据
this.$nextTick(() => {
// 在nextTick的回调中获取DOM中的数据
this.content2 = this.$refs.contentDiv.innerHTML
})
this.content3 = this.$refs.contentDiv.innerHTML
}
},
mount () {
this.changeContent()
}
}
</script>
打开网页后,我们可以发现结果为:
After NextTick
在nextTick执行前获取内容:Before NextTick
在nextTick执行之后获取内容:After NextTick
在nextTick执行前获取内容:Before NextTick
总结:
首先是在changeContent方法中对content进行了更改
虽然content1
和content3
获取内容都是再content
数据更改之后的,但是它们属于同一个事件循环,因此content1
和content3
获取的还是’ Before NextTick ', 而content2
获取内容的语句写在nextTick的回调中,在DOM更新之后执行,所以能够获取到更新后的 ’ After NextTick ’
应用场景
在created生命周期执行DOM操作
当在create()
生命周期函数中执行DOM操作是不可取的,因为此时的DOM并未进行任何的渲染.所以解决方法是将DOM操作写进Vue.nextTick()
的回调函数中,或者是将操作放入mounted()
钩子函数中
在数据变化后需要进行基于DOM结构的操作
在我们更新数据之后,如果还有操作要根据更新后的DOM节后进行操作,那么我们就应当将这部分操作放入 Vue.nextTick() 回调函数中
可能你还没有注意到,Vue 异步执行 DOM 更新。只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部尝试对异步队列使用原生的 Promise.then 和 MessageChannel,如果执行环境不支持,会采用 setTimeout(fn, 0) 代替。
例如,当你设置 vm.someData = ‘new value’ ,该组件不会立即重新渲染。当刷新队列时,组件会在事件循环队列清空时的下一个“tick”更新。多数情况我们不需要关心这个过程,但是如果你想在 DOM 状态更新后做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员沿着“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们确实要这么做。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用
总结
- 在同一事件循环中,当所有的同步数据更新执行完毕后,才会调用nextTick
- 在同步执行环境中的数据完全更新完毕之后,DOM才会开始渲染
- 在同一个事件循环中,若出现多个nextTick,将会按最初的执行顺序进行调用
- 每个异步回调函数执行后都会存在一个独立的事件循环中,对应自己独立的nextTick