vue.$nextTick

定义:在下次dom更新循环结束之后执行延迟回调。
理解:可以在这个方法中获取更新后的dom。

<template>
    <button ref="btn" @click="add">count: {{count}}</button>
</template>

<script>
export default {
    data(){
        return {
            count: 0,
        }
    },
    methods:{
        add(){
            this.count ++;
            console.log(this.count, this.$refs.btn.innerText);// 1 "count: 0"
            
            this.$nextTick(()=>{
                console.log(this.$refs.btn.innerText)// count: 1
            })
        }
    }
}
</script>

上面例子中可以看到,更新count之后立即打印dom中的内容,是更新之前的,而nextTick中可以获取到更新后的dom。

原理:
vue是异步执行dom更新的,一旦检测到数据变化,vue就会开启一个队列,然后把在同一事件循环中观察到数据变化的watcher推送进这个队列。如果这个watcher被触发多次,只会被推送到队列一次。这种缓冲行为可以有效去掉重复数据造成的不必要的计算和dom操作,比如

<template>
    <div>{{count}}</div>
</template>
<script>
export default{
    data(){
        return {
            count: 0
        }
    },
    created(){
        for(let i = 0; i < 10000; i++){
            this.count ++
        }
    }
}
</script>

上面例子中,created时count会执行10000次++操作,每次++都会依次触发 setter-dep-watcher-update-patch,如果没有异步更新视图,那么每一次++操作都会更新dom,非常消耗性能。
所以vue实现了一个queue队列,在下一个事件循环时,会清空队列,并且更新dom。同时,有用相同id的watcher不会被重复添加到队列中,所以不会执行10000次watcher的count

使用场景:
需要操作因数据而改变的dom。

实现下图的对话窗口页时,因为无法提前知道接口返回html片段内容的高度,所以需要在消息列表更新dom之后,根据高度判断是否需要折叠和显示折叠按钮,这时就可以用到vue.nextTick
image

this.$nextTick(()=>{
    let lastIndex = this.QAlist.length / 2 - 1;
    let par_msg = document.getElementsByClassName("service-chat-content")[
        this.QAlist.length
    ];
    let height = par_msg.getElementsByClassName("msg-content")[0]
        .offsetHeight;
    if (height > 400) {
        par_msg.classList.add("collapse");
        let open = document.createElement("DIV");
        open.innerText = "展开";
        open.classList.add("open");
        open.addEventListener("click", e => {
          if (open.innerText === "展开") {
            open.innerText = "收起";
            par_msg.style.height = "auto";
          } else {
            open.innerText = "展开";
            par_msg.style.height = "400px";
          }
        });
        par_msg.appendChild(open);
      }
    }
});