Vue--nextTick--作用/用法/原理
原文网址:Vue--nextTick--作用/用法/原理_IT利刃出鞘的博客-CSDN博客
简介
说明
本文介绍Vue的nextTick的作用、用法、原理。
官网网址
API — Vue.js
作用
Vue 在修改数据后,视图(DOM)不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。所以,在修改数据之后立马读取DOM是获取不到新数据的,获取到的是原来的DOM数据。
使用nextTick可以获得DOM更新后的数据。在下次 DOM 更新之后Vue会回调nextTick指定的函数。可以在修改数据之后立即使用这个nextTick方法,在指定的函数里获取更新后的 DOM。
实例
1:修改数据后读取DOM
说明
修改数据后立马读DOM是读不到的(读到的是旧数据),用nextTick才能读到。
代码
<template>
<div class="outer">
<div>
这是CompA
</div>
<h3 ref="hello">{{ value1 }}</h3>
<button @click="testClick()">测试一下</button>
</div>
</template>
<script>
export default {
name: 'CompA',
data () {
return {
value1: 'aa'
}
},
methods: {
testClick () {
this.value1 = 'bb'
console.log('直接获取:' + this.$refs.hello.innerText)
this.$nextTick(() => {
console.log('nextTick获取:' + this.$refs.hello.innerText)
})
}
}
}
</script>
<style scoped>
.outer {
margin: 20px;
border: 2px solid blue;
padding: 20px;
}
</style>
测试
可以发现,直接读取值读到的是旧值,用nextTick获取到的是新值。
2:created获取DOM
说明
我们知道,created被回调时,DOM还没有创建,读不到DOM,可以使用nextTick。
代码
<template>
<div class="outer">
<div>
这是CompA
</div>
<h3 ref="hello">{{ value1 }}</h3>
</div>
</template>
<script>
export default {
name: 'CompA',
data () {
return {
value1: 'aa'
}
},
created () {
console.log('created进入:' + this.$refs.hello)
this.$nextTick(() => {
console.log('created的nextTick:' + this.$refs.hello)
})
},
mounted () {
console.log('mounted进入:' + this.$refs.hello)
this.$nextTick(() => {
console.log('mounted的nextTick:' + this.$refs.hello)
})
}
}
</script>
<style scoped>
.outer {
margin: 20px;
border: 2px solid blue;
padding: 20px;
}
</style>
结果
可以发现,在created中获取不到DOM,但在created里使用nextTick可以获取到。
Vue异步执行原理
- 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
- 异步任务有了运行结果时在"任务队列"(task queue)中放置一个事件。
- "执行栈"中的所有同步任务执行完毕时,系统会读取"任务队列"。对应的异步任务结束等待状态,进入执行栈,开始执行。
- 主线程不断重复上面的第三步。
第一个 tick(图例中第一个步骤,即'本次更新循环'):
- 首先修改数据,这是同步任务。同一事件循环的所有的同步任务都在主线程上执行,形成一个执行栈,此时还未涉及 DOM 。
- Vue 开启一个异步队列,并缓冲在此事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。
第二个 tick(图例中第二个步骤,即'下次更新循环'):
- 同步任务执行完毕,开始执行异步 watcher 队列的任务,更新 DOM 。Vue 在内部尝试对异步队列使用原生的 Promise.then 和 MessageChannel 方法,如果执行环境不支持,会采用 setTimeout(fn, 0) 代替。
第三个 tick(图例中第三个步骤):
- 此时就是文档所说的:“下次 DOM 更新循环结束之后”
- 此时通过 Vue.nextTick 获取到改变后的 DOM 。通过 setTimeout(fn, 0) 也可以同样获取到。
简单总结事件循环:
- 同步代码执行
- 查找异步队列,推入执行栈,执行Vue.nextTick[事件循环1]
- 查找异步队列,推入执行栈,执行Vue.nextTick[事件循环2]...
总之,异步是单独的一个tick,不会和同步在一个 tick 里发生,也是 DOM 不会马上改变的原因。
在一个tick中多次更新数据页面只更新一次
即使在 Vue 中多么频繁地修改数据,最后 Vue 页面只会更新一次。
例如:
- 数据 name 被 页面引用,name 会收集到 页面的 watcher;
- name 被修改时,会通知所有收集到的 watcher 进行更新(watcher.update);
- 如果name 一时间被修改三次时,按道理应该会通知三次 watcher 更新,那么页面会更新三次,但是最后只会更新一次。
这是因为:
- 当数据变化后,把 watcher.update 函数存放进 nextTick 的 回调数组中,并且会做过滤。
- 通过 watcher.id 来判断 回调数组 中是否已经存在这个 watcher 的更新函数,不存在,才 push。
- 之后 nextTick时 遍历回调数组,便会执行更新。
所以当三次修改数据的时候,会 push 回调数组 三个 watcher.update,但是只有第一次是 push 成功的,其他的会被过滤掉,因为已经存在了。
不管你修改多少次数据,nextTick 的回调数组中只存在唯一一个 watcher.update,从而页面只会更新一次。