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()
        })
      })
    })
  },

 不是标签选择器的问题哦,哎。。。。

posted @ 2019-08-16 15:50  番茄土豆西红柿  阅读(559)  评论(0)    收藏  举报
TOP