27-Vue.js中this.$nextTick()的使用
原文:https://www.cnblogs.com/jin-zhe/p/9985436.html
this.$nextTick()将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。
假设我们更改了某个dom元素内部的文本,而这时候我们想直接打印出这个被改变后的文本是需要dom更新之后才会实现的,也就好比我们将打印输出的代码放在setTimeout(fn, 0)中;
先来第一个例子看一看
<template>
<section>
<div ref="hello">
<h1>Hello World ~</h1>
</div>
<el-button type="danger" @click="get">点击</el-button>
</section>
</template>
<script>
export default {
methods: {
get() {
}
},
mounted() {
console.log(333);
console.log(this.$refs['hello']);
this.$nextTick(() => {
console.log(444);
console.log(this.$refs['hello']);
});
},
created() {
console.log(111);
console.log(this.$refs['hello']);
this.$nextTick(() => {
console.log(222);
console.log(this.$refs['hello']);
});
}
}
</script>

可以根据打印的顺序看到,在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作并无作用,而在created()里使用this.$nextTick()可以等待dom生成以后再来获取dom对象
然后来看第二个例子
<template>
<section>
<h1 ref="hello">{{ value }}</h1>
<el-button type="danger" @click="get">点击</el-button>
</section>
</template>
<script>
export default {
data() {
return {
value: 'Hello World ~'
};
},
methods: {
get() {
this.value = '你好啊';
console.log(this.$refs['hello'].innerText); # 这里打印的是Hello World ~
this.$nextTick(() => {
console.log(this.$refs['hello'].innerText); # 更新,dom渲染后这里打印的是"你好啊"
});
}
},
mounted() {
},
created() {
}
}
</script>

根据上面的例子可以看出,在方法里直接打印的话, 由于dom元素还没有更新, 因此打印出来的还是未改变之前的值,而通过this.$nextTick()获取到的值为dom更新之后的值
this.$nextTick()在页面交互,尤其是从后台获取数据后重新生成dom对象之后的操作有很大的优势,这里只是简单的例子,实际应用中更为好用~
我的使用场景:
参考 https://evolly.one/2019/03/29/57-el-tree-contextmenu/ 设置右键功能,但是发现
const tree = document.querySelectorAll('#el-tree [role="treeitem"]')
这块通过属性标签选择器一直选不到元素,一度怀疑是标签选择器的问题,查了半天。。。。。。。后来查到this.$nextTick() 这个事情才有了写突破性的进展,还原一下问题现场
参考上面文章中的代码,mounted的时候做如下操作
mounted() { this.initTreeData() // async + await 异步从后端服务器获取response数据赋值给树形插件绑定的data字段 this.getUserRole() // 从store.getters获取用户的角色,可忽略 this.$nextTick(() => { // vue-context-menu 需要传入一个触发右键事件的元素,等页面 dom 渲染完毕后才可获取 this.contextMenuTarget = document.querySelector("#el-tree") // 获取所有的 treeitem,循环监听右键事件 const tree = document.querySelectorAll('#el-tree [role="treeitem"]') tree.forEach(i => { i.addEventListener('contextmenu',event => { // 如果右键了,则模拟点击这个treeitem event.target.click() }) }) }) },
问题出在什么地方呢?问题是因为mounted中的代码是异步执行的,也就是说不会等this.initTreeData()执行完了,再执行下面的this.$nextTick() 函数,为了证明这一点,如下代码
methods: { ...... async initTreeData() { await getTreeData().then(response => { console.log('async init tree table') this.data = response.data; this.handleNodeClick(this.data[0]) //初始化展示的节点 }).catch(error => { console.log(error.response); }); }, ... }, mounted() { this.initTreeData() console.log('===============6666') this.getUserRole() },

可以看到先执行了mounted中的console,然后执行initTreeData中的console,所以是异步执行的,知道了这个原因,那么我们知道了 document.querySelectorAll('#el-tree [role="treeitem"]') 为什么没有我们想要的[role='treeitem']这个标签萱蕚的数据,因为initTreeData的这次数据更新到树形插件中,所以这个时候是获取不到[role='treeitem']这个的标签选择器对应的数据的的,可以获取的[role]这个标签选择器的数据呢?因为mounted本身会有一次DOM的更新操作,所以是在这个DOM更新操作后执行了this.initTreeData(),但是在initTreeData更新数据后,就不会执行mounted中的这个this.$nextTick()了,因为已经顺序执行完了啊。。。。。。
那么解决办法有什么呢?
1.将上面this.$nextTick()的逻辑放在initTreeData的逻辑中
async initTreeData() { await getTreeData().then(response => { console.log('async init tree table') this.data = response.data; this.handleNodeClick(this.data[0]) //初始化展示的节点 }).catch(error => { console.log(error.response); }); this.$nextTick(() => { // vue-context-menu 需要传入一个触发右键事件的元素,等页面 dom 渲染完毕后才可获取 this.contextMenuTarget = document.querySelector("#el-tree") // 获取所有的 treeitem,循环监听右键事件 const tree = document.querySelectorAll('#el-tree [role="treeitem"]') console.log(tree) tree.forEach(i => { i.addEventListener('contextmenu',event => { // 如果右键了,则模拟点击这个treeitem event.target.click() }) }) }) },
2.将上面的this.$nextTick()放在beforeUpdate的钩子函数中,也就是上面的是在dom更新后做操作,这个是在操作更新前做操作,效果上是一样的。
beforeUpdate() { this.$nextTick(() => { // vue-context-menu 需要传入一个触发右键事件的元素,等页面 dom 渲染完毕后才可获取 this.contextMenuTarget = document.querySelector("#el-tree") // 获取所有的 treeitem,循环监听右键事件 const tree = document.querySelectorAll('#el-tree [role="treeitem"]') console.log('before update start') console.log(tree) console.log('before update end') tree.forEach(i => { i.addEventListener('contextmenu',event => { // 如果右键了,则模拟点击这个treeitem event.target.click() }) }) }) },
不是标签选择器的问题哦,哎。。。。

浙公网安备 33010602011771号