this.$nextTick 将回调延迟到下次DOM更新循环之后执行。在修改数据之后立即使用它,然后等待DOM更新。

this.$nextTick 跟全局方法 vue.nextTick 一样,不同的是,回调的 this 自动绑定到调用它的实例上。

总的来说,假设我们更改了某个 dom 元素内部的文本,而这时候我们想直接打印这个更改之后的文本是需要 dom 更新之后才会实现的,就像我们把将要打印输出的代码放在 setTimeout(fn, 0) 中

未使用 
<template>
    <button ref="tar" 
	    type="button" 
	    name="button" 
	    @click="testClick">{{content}}</button>
</template>
 
<script>
    export default {
        data () {
            return {
                content: '初始值'
            }
        }
     methods: {
       testClick(){
         this.content = '改变了的值'
         // 这时候直接打印的话,由于dom元素还没更新
         // 因此打印出来的还是未改变之前的值
         console.log(that.$refs.tar.innerText) // 初始值
       }
     }
    }
</script>
使用

this.$nextTick这个方法作用是当数据被修改后使用这个方法会回调获取更新后的dom再渲染出来

methods:{
    testClick() {
        this.content = '改变了的值'
        this.$nextTick(() => {
            // dom元素更新后执行,因此这里能正确打印更改之后的值
            console.log(that.$refs.tar.innerText) // 改变了的值
        })
    }
}

案例进阶使用
<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>

vue项目中this.$nextTick()的用法-案例_setTimeout

可以根据打印的顺序看到,在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);
        this.$nextTick(() => {
          console.log(this.$refs['hello'].innerText);
        });
      }
    },
    mounted() {
    },
    created() {
    }
  }
</script>

vue项目中this.$nextTick()的用法-案例_回调延迟更新页面数据_02

上面的例子可以看出,在方法里直接打印的话, 由于dom元素还没有更新, 因此打印出来的还是未改变之前的值,而通过this.$nextTick()获取到的值为dom更新之后的值
this.$nextTick()在页面交互,尤其是从后台获取数据后重新生成dom对象之后的操作有很大的优势,

这里只是简单的例子,实际应用中更为好用~


拓展 $nextTick 与 setTimeout 的一点对比

如何在Vue 3的实例方法下发现$nextTick的,并大吃一惊。使用Vue已经有一段时间了,他已经习惯了把$watch和$emit写成实例方法。那么,$nextTick是用来做什么的?Vue文档说,它"[defers]回调,在下一个DOM更新周期后执行"。

如何尝试这样做的

this.loadingAnimation = true
this.startVeryLongCalculation()
this.completeVeryLongCalculation()
this.loadingAnimation = false

 有用。为什么?

nextTick做什么?

nextTick接受一个延迟到下一个DOM更新周期的回调函数。这只是Vue的一种说法,"嘿,如果你想在DOM更新后执行一个函数(这种情况很少发生),我希望你使用nextTick而不是setTimeout"。

Vue.nextTick(() => {}) // syntax

下面很快就会讲到setTimeout和nextTick参数。我们用这个例子来可视化nextTick的行为:

<template>
  <div>
    {{ currentTime }}
  </div>
</template>

<script>
export default {
  name: 'getCurrentTime',
  data() {
    return {
      currentTime: ''
    }
  },
  mounted() {
    this.currentTime = 3;

    this.$nextTick(() => {
        let date = new Date()
        this.currentTime = date.getFullYear()
    });
  }
}
</script>

在J电脑上运行这个代码片段。它将显示2021年。并不是说如果你去掉nextTick,就不会得到同样的结果。然而,你应该明白,Vue会根据数据中的内容对DOM进行修改。

在上面的代码片段中,Vue将DOM更新为3,然后调用回调,将DOM更新为2021,最后将控制权交给浏览器,浏览器将显示2021。

到目前为止,我们已经研究了nextTick在回调队列中插入回调函数并在适当的时候执行该函数。

这个你可能会感兴趣,nextTick中的回调是作为事件循环中的一个微任务使用的。nextTick的源代码明确指出,"nextTick行为利用了微任务队列,可以通过本地的Promise.then或MutationObserver来访问。"

setTimeout vs nextTick

在DOM更新后执行函数的另一种方法是使用JavaScript的setTimeout()函数。

我们将上面的代码用setTimeout替换nextTick:

<template>
  <div>
    {{ currentTime }}
  </div>
</template>

<script>
export default {
  name: 'getCurrentTime',
  data() {
    return {
      currentTime: ''
    }
  },
  mounted() {
    this.currentTime = 3;

    setTimeout(() => {
      let date = new Date()
      this.currentTime = date.getFullYear()
    }, 0);
  }
}
</script>

运行此代码片段。首先看到3然后2021。它发生得很快,因此如果没有看到此行为,需要刷新浏览器。

在上面的代码片段中,Vue将DOM更新为3,并提供浏览器控制。然后浏览器显示3,调用回调函数,将DOM更新到2021,最后将控制权交给浏览器,现在浏览器显示2021。

nextTick的实现在不支持Promise和MutationObserver的浏览器(IE 6-10和Opera Mini浏览器)上,使用setTimeout作为后备方法,对于不支持Promise和MutationObserver的浏览器(IE 10),它更倾向于setImmediate。

何时使用 nexttick
  • 当你想使用setTimeout时
  • 当你想确定DOM能反映你的数据时
  • 在尝试执行异步操作时,遇到Uncaught (in promise) DOMException等错误。记住,Vue是异步更新DOM的

最后来个示例:

<div id="app">
  <div ref="listScroll" class="scrolledList">
    <ul ref="scrolledHeight">
      <li v-for="month in months">
        {{month}}
      </li>               
    </ul>
  </div>

  <input type="text" placeholder="Add Month" v-model="month">
  <button @click="addMessage" @keyup.enter="addMessage"> Add Month</button>
</div>

<script src="https://unpkg.com/vue@next"> 
  Vue.createApp({
    data() {
      return {
        month: '',
        months: ['Jan', 'Feb', 'Apr', 'May', 'June', 'July', 'Aug']
      }
    },
    mounted() {
      this.updateScrollNextTick()
    },
    methods: {
      addMessage() {
        if(this.month == ''){
          return
        }

        this.months.push(this.month)
        this.month = ''
        this.updateScrollNextTick()
      },
      updateScrollNextTick () {
        let scrolledHeight = this.$refs.scrolledHeight.clientHeight

        this.$nextTick(() => {
          this.$refs.listScroll.scrollTo({
            behavior: 'smooth',
            top: scrolledHeight
          })
        })
      }
    },
  })
  .mount("#app")
</script>

在线演示

vue项目中this.$nextTick()的用法-案例_javascript_03

在上面的代码片断中,我们想在一个新项目被添加到列表中时获得平滑的向下滚动效果。浏览一下代码,尝试修改一下,去掉nextTick,你就会失去那种平滑的滚动效果。你也可以尝试用setTimeout来代替nextTick