简单了解Vue.nextTick

本文最后更新于:3 年前

引言

在日常开发中,理解前端框架处理数据和视图更新的机制是非常重要的,尤其是在处理复杂交互和动态内容时。本文通过一个具体的问题实例,探讨了Vue.js中的nextTick函数的重要性和应用。

问题复现

demo:通过v-if渲染table组件后,获取这个组件的引用,并对其进行下一步操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template>
<div>
<el-table v-if="tableVisible" ref="table">
<el-table-column prop="xxx" label="xxx" width="180"></el-table-column>
</el-table>
</div>
</template>

<script>
export default {
name: "Test",
data() {
return {
tableVisible: false,
}
},
methods: {
showTable() {
this.tableVisible = true;
const table = this.$refs['table'];
console.log(table);
}
}
}
</script>

当触发showTable方法时,控制台打印为:

1
undefined

当再次点击的时候,又能获取组件的引用

1
VueComponent {_uid: 2, _isVue: true, __v_skip: true, _scope: EffectScope, $options: {…}, …

这与预想的不太一样,预想中第一次触发方法就能获取到组件的引用

最终解决方法为:将获取DOM作为Vue.nextTick(callback)的回调函数逻辑,即

1
2
3
4
5
6
7
showTable() {
this.tableVisible = !this.tableVisible;
this.$nextTick(() => {
const table = this.$refs['table'];
console.log(table)
})
}

此时就跟预期一样,页面刷新后第一次点击按钮就能获取到组件的引用

原理探究

在Vue官方文档《深入响应式原理》中有这样一段话:

可能你还没有注意到,Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.thenMutationObserversetImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。

例如,当你设置 vm.someData = 'new value',该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环“tick”中更新。多数情况我们不需要关心这个过程,但是如果你想基于更新后的 DOM 状态来做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员使用“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们必须要这么做。为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用

理解:

  1. 异步更新机制:Vue 更新 DOM 的过程是异步执行的。这意味着当数据变化时,Vue 不会立即更新 DOM,而是把这些数据变更的操作放入一个队列中。在同一个事件循环(event loop)中,所有的数据变更都会被缓冲到这个队列里。这个机制能有效地整合在短时间内多次数据变更的操作,避免不必要的 DOM 更新,从而提升性能。
  2. 去重操作:如果在同一个事件循环中一个数据依赖(watcher)被多次触发,它只会被推入队列一次。这种去重是非常重要的,因为它减少了不必要的计算和 DOM 操作。
  3. 事件循环的下一个“tick”:Vue 会在事件循环的下一个“tick”中,处理并刷新这个队列,执行实际的(已去重的)DOM 更新。Vue 内部使用原生的 Promise.thenMutationObserversetImmediate 来管理这个异步队列。如果这些都不可用,它会使用 setTimeout(fn, 0)
  4. Vue.nextTick() 的使用:在实际开发中,有时候我们需要在 Vue 更新 DOM 之后立即执行某些操作。由于 Vue 的 DOM 更新是异步的,直接在数据变更后操作 DOM 可能得不到更新后的结果。Vue 提供了 Vue.nextTick(callback) 方法来解决这个问题。这个方法会在 DOM 更新完成后执行提供的回调函数,确保你可以操作最新的 DOM 状态。

这个机制是Vue.js提高性能和效率的关键,同时它也支持了Vue.js的数据驱动的设计哲学,即尽可能避免直接操作 DOM,而是通过数据来控制UI。

使用场景

  1. 手动触发等待 DOM 更新:nextTick() 最直接的用途。当数据改变后,想要立即基于新的 DOM 状态执行某些操作时,nextTick() 是必要的。

  2. Vue生命周期钩子函数如果要操作DOM,一定要放在Vue.$nextTick的回调函数中,确保数据变更后拿到最新的DOM。

其他

Vue.$nextTick返回的是一个Promise对象,因此可以使用ES2017 async/await完成相同的事情,如

1
2
3
4
5
6
async showTable() {
this.tableVisible = !this.tableVisible;
await this.$nextTick();
const table = this.$refs['table'];
console.log(table)
}

此时同样可以获得table组件的DOM引用。

总结

对于后端开发人员来说,虽然主要关注的是服务器、数据库和API的开发,但对前端框架的基本理解将极大地促进开发流程和跨团队协作。本文是一篇后端开发者遇到的前端问题解析,通过分析Vue.js的nextTick功能,展示了如何处理Vue组件和DOM更新的典型问题。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!