Vue.$nextTick你真的懂了吗?
在使用vue的时候,不知道大家有没有遇到这种情况,明明对这个数据进行更改了,但是当我获取它的时候怎么是上一次的值~~
vue的官方文档是这样说的:
可能你还没有注意到,Vue异步执行DOM更新。只要观察到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个watcher被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作上非常重要。然后,在下一个的事件循环“tick”中,Vue刷新队列并执行实际 (已去重的) 工作。
白话一点就是说,Vue不可能对每一个数据变化都做一次渲染,它会把这些变化先放在一个异步的队列当中,同时它还会对这个队列里面的操作进行去重,比如你修改了这个数据三次,它只会保留最后一次。这些变化是都可以通过队列的形式保存起来,那现在的问题就来到了,那vue是在事件循环的哪个时机来对DOM进行修改呢?
我们都知道在一轮事件循环中,同步执行栈中代码执行完成之后,才会执行异步队列当中的内容,那我们获取DOM的操作是一个同步的呀!!那岂不是虽然我已经把数据改掉了,但是它的更新异步的,而我在获取的时候,它还没有来得及改,所以会出现文章开头的那个问题。就轮到Vue.$nextTick( )出场了~
Vue.$nextTick( )
vue在调用Watcher更新视图时,并不会直接进行更新,而是把需要更新的Watcher加入到queueWatcher队列里,然后把具体的更新方法flushSchedulerQueue 传给nexTick 进行调用。
注意 :nextTick 只是单纯通过Promise、setTimeout等方法模拟的异步任务。
class Watcher {
// 执行更新操作
update() {
queueWatcher(this);
}
}
const queue = [];
function queueWatcher(watcher: Watcher) {
// 将当前 Watcher 添加到异步队列
queue.push(watcher);
//执行异步队列,并传入回调
nextTick(flushSchedulerQueue);
}
// 更新视图的具体方法
function flushSchedulerQueue() {
let watcher, id;
// 排序,先渲染父节点,再渲染子节点,避免不必要的子节点渲染
queue.sort((a, b) => a.id - b.id);
// 遍历所有 Watcher 进行批量更新。
for (index = 0; index < queue.length; index++) {
watcher = queue[index];
// 更新 DOM
watcher.run();
}
}
在这个过程中, 会完善下列逻辑判断:
判断has 标识,避免在一个Queue 中添加相同的Watcher;
判断waiting 标识,让所有的Watcher 都在一个tick 内进行更新;
判断flushing 标识,处理Watcher 渲染时,可能产生的新Watcher。

也就是说,调用this.$nextTick 其实就是调用图中的nextTick 方法,在异步队列中执行回调函数。根据先进先出 原则,修改Data 触发的更新异步队列会先得到执行,执行完成后就生成了新的DOM,接下来执行this.$nextTick 的回调函数时,能获取到更新后的DOM元素了。
应用场景
created生命周期中
在created()生命周期钩子函数中,dom还没有被渲染,此时在该钩子函数中进行dom赋值数据(或者其它dom操作)时无异于徒劳。所以,此时this.$nextTick()就会被大量使用。
<template>
<section>
<div ref="hello">
<h1>Hello mimi ~</h1>
</div>
<button type="danger" @click="get">点击</button>
</section>
</template>
<script>
export default {
name:'tick',
methods: {
get() {
}
},
created() {
console.log('在created直接进行dom操作');
console.log(this.$refs['hello']);
this.$nextTick(() => {
console.log('在nextTick回调中执行dom操作');
console.log(this.$refs['hello']);
});
}
}
</script>

对更改后的dom进行操作
<template>
<section>
<h1 ref="hello">{{ value }}</h1>
<button type="danger" @click="get">点击</button>
</section>
</template>
<script>
export default {
data() {
return {
value: 'Hello mimi ~'
};
},
methods: {
get() {
this.value = '奔赴热爱';
console.log('更改后直接获取:',this.$refs['hello'].innerText);
this.$nextTick(() => {
console.log('在nextTick中获取',this.$refs['hello'].innerText);
});
}
}
}
</script>

自己项目中遇到的例子

这个demo是一个订餐管理系统,该组件是评论模块。有全部,吐槽,满意三个按钮,点击哪一个,对应类型的评论会显示在下方。incrementTotal是按钮绑定的点击事件,点击之后,页面下方评论所对应的数据发生变化,但不能立即更新的页面上,所以放入nextTick回调中,进行滚动条的更新。
