nextTick
nextTick
例
<template> <view class="app"> <view ref="msgview" style="color:red;">{{msg}}</view> <view v-if="msg1">Message got outside $nextTick: {{msg1}}</view> <view v-if="msg2">Message got inside $nextTick: {{msg2}}</view> <view v-if="msg3">Message got outside $nextTick: {{msg3}}</view> <button @click="changeMsg"> Change the Message </button> </view> </template> <script> export default { data() { return { msg: 'Hello Vue.', msg1: '1', msg2: '2', msg3: '3' }; }, methods: { changeMsg() { this.msg = "Hello world."; this.msg1 = this.$refs.msgDiv.innerHTML; console.log(this.$refs) this.$nextTick(() => { this.msg2 = this.$refs.msgDiv.innerHTML; }) this.msg3 = this.$refs.msgDiv.innerHTML; } }, } </script>
图例
Hello world.
Message got outside $nextTick : Hello Vue
Message got inside $nextTick Hello world
Message got outside $nextTick: Hello Vue.
由上可知,msg1和msg3显示的内容还是变换之前的,而msg2显示的内容是变换之后的。
应用场景 :
在vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中,在created()钩子函数执行的时候DOM其实并未进行任何渲染, 而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js的代码放进Vue.nextTick()的回调函数中。与之对应的是mounted()钩子函数,因为该钩子函 数执行时所有的DOM挂载和渲染都已完成,此时在钩子函数中进行任何DOM操作都不会有问题。
Vue异步执行DOM更新。只要观察到数据变化,Vue将开启一个队列,并缓冲在同一时间循环中发生的所有数据改变。如果同一个watcher被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作上非常重要。然后,在下一个的事件循环“tick”中,Vue刷新队列并执行实际已去重的工作。
Vue在内部尝试使用对异步队列使用原生的promise.then和MessageChannel,如果执行环境不支持,会采用setTimeout(fn,0)代替。
首先,先了解 nextTick 中定义的三个重要变量。
- callbacks
用来存储所有需要执行的回调函数
- pending
用来标志是否正在执行回调函数
- timerFunc
用来触发执行回调函数
接下来,了解 nextTickHandler() 函数。
function nextTickHandler () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } }
<div id=app> <div id="div" v-if="showDiv">我是显示文本</div> <button @click="showAndGetText">获取内容</button > </div> <script> var app = new Vue({ el: '#app', data () { return { showDiv : false }, methods: { showAndGetText () { this.showDiv = true let text = document.getElementById('div').innerHTML console.log(text) } } } }) </script>
这段代码并不难理解,但是控制台回抛出一个 innerHTML of null 的错误,因为此时页面并未完成渲染,它并没有获取到div元素,这里涉及到一个Vue的重要概念:异步更新队列。
Vue在观察到数据变化时并不是直接更新DOM,而是开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。在缓冲时会去除重复的数据,这样避免了不必要的计算和DOM操作。然后,在下一个时间循环Tick中,Vue刷新队列并执行已去重的工作。所以,如果你用一个for循环来动态改变数据100次,其实它只会应用最后一次改变,如果没有这种机制,DOM就要重绘100次,这是我们不愿意看到的。
知道了Vue异步更新DOM的原理之后,上面的现象就不难理解了,事实上在下面语句执行时,,div仍然是没有被创建出来的,下面的
xthis.showDiv = true
let text =document.getElementById('div').innerHTML console.log(text);
仍然读取的是这一次事件循环的DOM,而实际上在这一次事件循环中,DOM并没有更新,所以是读取不到的。我们需要等下一个Vue事件循环,DOM更新完成后再读取,就可以读取到了