Advertisement

Vue.$nextTick你真的懂了吗?

阅读量:

在使用vue的时候,不知道大家有没有遇到这种情况,明明对这个数据进行更改了,但是当我获取它的时候怎么是上一次的值~~

vue的官方文档是这样说的:

可能你还没有注意到,Vue异步执行DOM更新。只要观察到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个watcher被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作上非常重要。然后,在下一个的事件循环“tick”中,Vue刷新队列并执行实际 (已去重的) 工作。

白话一点就是说,Vue不可能对每一个数据变化都做一次渲染,它会把这些变化先放在一个异步的队列当中,同时它还会对这个队列里面的操作进行去重,比如你修改了这个数据三次,它只会保留最后一次。这些变化是都可以通过队列的形式保存起来,那现在的问题就来到了,那vue是在事件循环的哪个时机来对DOM进行修改呢?
我们都知道在一轮事件循环中,同步执行栈中代码执行完成之后,才会执行异步队列当中的内容,那我们获取DOM的操作是一个同步的呀!!那岂不是虽然我已经把数据改掉了,但是它的更新异步的,而我在获取的时候,它还没有来得及改,所以会出现文章开头的那个问题。就轮到Vue.$nextTick( )出场了~

Vue.$nextTick( )

vue在调用Watcher更新视图时,并不会直接进行更新,而是把需要更新的Watcher加入到queueWatcher队列里,然后把具体的更新方法flushSchedulerQueue 传给nexTick 进行调用。

注意 :nextTick 只是单纯通过Promise、setTimeout等方法模拟的异步任务。

复制代码
    class Watcher {
      // 执行更新操作
      update() {
    queueWatcher(this);
      }
    }
    const queue = [];
    function queueWatcher(watcher: Watcher) {
      // 将当前 Watcher 添加到异步队列
      queue.push(watcher);
      //执行异步队列,并传入回调
      nextTick(flushSchedulerQueue);
    }
    // 更新视图的具体方法
    function flushSchedulerQueue() {
      let watcher, id;
      // 排序,先渲染父节点,再渲染子节点,避免不必要的子节点渲染
      queue.sort((a, b) => a.id - b.id);
      // 遍历所有 Watcher 进行批量更新。
      for (index = 0; index < queue.length; index++) {
    watcher = queue[index];
    // 更新 DOM
    watcher.run();
      }
    }

在这个过程中, 会完善下列逻辑判断:

判断has 标识,避免在一个Queue 中添加相同的Watcher;

判断waiting 标识,让所有的Watcher 都在一个tick 内进行更新;

判断flushing 标识,处理Watcher 渲染时,可能产生的新Watcher。
在这里插入图片描述
也就是说,调用this.$nextTick 其实就是调用图中的nextTick 方法,在异步队列中执行回调函数。根据先进先出 原则,修改Data 触发的更新异步队列会先得到执行,执行完成后就生成了新的DOM,接下来执行this.$nextTick 的回调函数时,能获取到更新后的DOM元素了。

应用场景

created生命周期中

created()生命周期钩子函数中,dom还没有被渲染,此时在该钩子函数中进行dom赋值数据(或者其它dom操作)时无异于徒劳。所以,此时this.$nextTick()就会被大量使用。

复制代码
    <template>
      <section>
    <div ref="hello">
      <h1>Hello mimi ~</h1>
    </div>
    <button type="danger" @click="get">点击</button>
      </section>
    </template>
    <script>
      export default {
      name:'tick',
    methods: {
      get() {
      }
    },
    created() {
      console.log('在created直接进行dom操作');
      console.log(this.$refs['hello']);
      this.$nextTick(() => {
        console.log('在nextTick回调中执行dom操作');
        console.log(this.$refs['hello']);
      });
    }
      }
    </script>
在这里插入图片描述

对更改后的dom进行操作

复制代码
    <template>
      <section>
    <h1 ref="hello">{{ value }}</h1>
    <button type="danger" @click="get">点击</button>
      </section>
    </template>
    <script>
      export default {
    data() {
      return {
        value: 'Hello mimi ~'
      };
    },
    methods: {
      get() {
        this.value = '奔赴热爱';
        console.log('更改后直接获取:',this.$refs['hello'].innerText);
        this.$nextTick(() => {
          console.log('在nextTick中获取',this.$refs['hello'].innerText);
        });
      }
    }
      }
    </script>
在这里插入图片描述

自己项目中遇到的例子

在这里插入图片描述
这个demo是一个订餐管理系统,该组件是评论模块。有全部,吐槽,满意三个按钮,点击哪一个,对应类型的评论会显示在下方。incrementTotal是按钮绑定的点击事件,点击之后,页面下方评论所对应的数据发生变化,但不能立即更新的页面上,所以放入nextTick回调中,进行滚动条的更新。

全部评论 (0)

还没有任何评论哟~