Vue.nextTick的原理和用途

 

 

 

 

 

 

 

 

 

 

根据 HTML Standard,在每个 task 运行完以后,UI 都会重渲染,那么在 micro task 中就完成数据更新,当前 task 结束就可以得到最新的 UI 了。反之如果新建一个 task 来做数据更新,那么渲染就会进行两次。

micro task的这一特性是做队列控制的最佳选择,vue进行DOM更新内部也是调用nextTick来做异步队列控制。而当我们自己调用nextTick的时候,它就在更新DOM的那个micro task后追加了我们自己的回调函数,从而确保我们的代码在DOM更新后执行。

比如一段时间内,你无意中修改了最初代码片段中的 msg多次,其实只要最后一次修改后的值更新到DOM就可以了,假如是同步更新的,每次 msg 值发生变化,那么都要触发 setter->Dep->Watcher->update->patch ,这个过程非常消耗性能。

 

 

 

 

vm.$nextTick( [callback] )

官方解释:将回调延迟到下次DOM更新循环之后执行。

要理解这句话,首先要了解一下vue的异步更新队列,vue异步执行dom更新。只要观察到数据变化,不会立即更新DOM,vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。

如果同一个数据被多次改变,只会被推到队列中一次。例如,当你设置vm.someData =  'new value',对应的dom更新会被推到一个队列里,该组件不会立即重新渲染,会在当前tick完毕后,在下一个tick中渲染dom。在事件循环中,每进行一次循环操作称为tick。而nextTick函数就是vue提供的一个实例方法,数据更新后等待下一个tick里dom更新完后执行回调,回调的this自动绑定到调用它的实例上。

例如:

html: 
<span class="test">{{egData}}</span>
<el-button @click="changeData">改变</el-button>

js:
new Vue({
    data () {
        return {
            egData: 'old Message'
        }
    }
    methods: {
        changeData () {
          this.egData = 'new Message'
          console.log($('.test').html(), '-----------------------')
        }
    }
})

结果: 第一次点击输出 old Message -----------------------,第二次点击输出 new Message -----------------------

使用$nextTick:

js:
new Vue({
    data () {
        return {
            egData: 'old Message'
        }
    }
    methods: {
        changeData () {
          this.egData = 'new Message'
          this.$nextTick(function () {
            console.log($('.test').html(), '-----------------------')
          })

        }
    }
})

结果:不管第几次点击,都输出 new Message -----------------------

 

$nextTick使用场景:

1、数据更新后想要马上操作新的DOM,需要把操作写在nextTick的回调里

2、在created钩子函数里需要操作DOM,也可以把操作写在nextTick的回调里,(created钩子函数里还没有挂载dom,所以直接操作会有问

 

 

 

this.$nextTick()将回调延迟到下次DOM更新循环之后执行。在修改数据之后立即使用它,然后等待DOM更新。它跟全局方法Vue.nextTick一样,不同的是回调的this自动绑定到调用它的实例上。

 

nextTick的由来:由于VUE的数据驱动视图更新,是异步的,即修改数据的当下,视图不会立刻更新,而是等同一事件中的所有数据变化完成之后,再统一进行视图更新。

nextTick的触发时机:在同一事件循环中的数据变化后,DOM完成更新,立即执行nextTick(callback)内的回调。

应用场景:需要在视图更新之后,基于新的视图进行操作。

以上出现了事件循环的概念,其涉及到JS的运行机制,包括主线程的执行栈、异步队列、异步API、时间循环的写作。大致可以理解为:主线程完成同步环境执行,查询任务队列,提取队首的任务,放入主线程中执行;执行完毕,再重复该操作,该过程称为事件循环。而主线程的每次读取任务队列操作,是一个事件循环的开始。异步callback不可能处在同一事件循环中。

简单总结事件循环:同步代码执行→查找异步队列,推入执行栈,执行callback[事件循环1]→查找异步队列,推入执行栈,执行callback2[事件循环2]...

即每个异步callback,最终都会形成自己独立的一个事件循环。

结合nextTick的由来,可以推出每个事件循环中,nextTick触发的时机:同一事件循环中的代码执行完毕→DOM更新→nextTick callback触发

ps:上文中的任务队列、消息队列、异步队列指向同一个东西,均指macrotask queue。

 

用法:在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM。

 

  • 在Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中

created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之对应的就是mounted()钩子函数,因为该钩子函数执行时所有的DOM挂载和渲染都已完成,此时在该钩子函数中进行任何DOM操作都不会有问题 。

  • 在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作都应该放进Vue.nextTick()的回调函数中


 
 
为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>nextTick</title>
        <script src="./js/vue.min.js"></script>
    </head>
    <body>
        
        <div class="app">
          <div ref="msgDiv">{{msg}}</div>
          <div v-if="msg1">Message got outside 1 $nextTick: {{msg1}}</div>
          <div v-if="msg2">Message got inside 2 $nextTick: {{msg2}}</div>
          <div v-if="msg3">Message got outside 3 $nextTick: {{msg3}}</div>
          <button @click="changeMsg">
            Change the Message
          </button>
        </div>
        <script>
            new Vue({
              el: '.app',
              data: {
                msg: 'Hello Vue.',
                msg1: '',
                msg2: '',
                msg3: ''
              },
              methods: {
                changeMsg() {
                  this.msg = "Hello world."
                  this.msg1 = this.$refs.msgDiv.innerHTML
                  this.$nextTick(() => {
                    this.msg2 = this.$refs.msgDiv.innerHTML
                  })
                  this.msg3 = this.$refs.msgDiv.innerHTML
                }
              }
            })

        
        </script>
    </body>
</html>

msg1和msg3显示的内容还是变换之前的,而msg2显示的内容是变换之后的。其根本原因是因为Vue中DOM更新是异步的

 

 

需要注意的是,在 created 和 mounted 阶段,如果需要操作渲染后的试图,也要使用 nextTick 方法。

官方文档说明:

注意 mounted 不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用 vm.$nextTick 替换掉 mounted
 
mounted: function () {
  this.$nextTick(function () {
    // Code that will run only after the
    // entire view has been rendered
  })
}


https://segmentfault.com/a/1190000012861862
思否参考↑
posted @ 2021-11-11 16:34  是桂  阅读(3281)  评论(0编辑  收藏  举报