唯雪博客

异步更新队列Vue.$nextTick 解决dom未渲染完毕,数据已更新带来的问题

最近遇到一个项目,需要循环渲染同一种echarts图表,根据数据动态更新图表。渲染完成后切换条件,再次刷新视图

 如下图的结构:

唯雪博客

为了提高复用性,我封装了一个组件,根据数组循环调用,渲染没有问题但是切换的时候单击会报错,双击才能正常切换。虽然对使用影响不大,但是体验太差,应该没有哪个用户受的了

因为使用echart绘制图表,必须要有一个已挂载在dom树上的父容器进行初始化。这里是组件循环调用,所以div父容器的id我动态的追加的

<div :id="id"  style="width:100%;height:280px;margin:0 auto"></div>

点击年份选项卡的时候,就动态改变传入的id。但是理想是丰满的,现实是残酷的

单击选项卡,报错,debug发现,id已经改变,检测dom的id也是更新了但是使用echart初始化始终报如下图所示的错误:

唯雪博客

let myChart = echarts.init(document.getElementById(this.id))

一开始一直以为是echart初始化的问题,断点追踪,上一句id已更新,但是获取dom始终是null

这时候我意识可能不是echart的问题,应该是vue种存在某种机制,更新dom与数据不是同步的,dom的更新速度没有跟上数据更新数据,所以使用动态id才会获取不到dom结构。

但是在debug过程中发现,每次执行vue.js以下代码块的时候就会出错

/**
* Run the watchers in a single queue.
*
* @param {Array} queue
*/
//这里就是循环更新异步队列中的
watcherfunction runBatcherQueue (queue) {
  // do not cache length because more watchers might be pushed
  // as we run existing watchers
  for (let i = 0; i < queue.length; i++) {
    var watcher = queue[i]
    var id = watcher.id
    has[id] = null
    watcher.run()
    // in dev build, check and stop circular updates.
    if (process.env.NODE_ENV !== 'production' && has[id] != null) {
      circular[id] = (circular[id] || 0) + 1
      if (circular[id] > config._maxUpdateCount) {
        warn(
          'You may have an infinite update loop for watcher ' +
          'with expression "' + watcher.expression + '"',
          watcher.vm
        )
        break
      }
    }
  }
  queue.length = 0
}

实在看不懂源码,只有去看文档,看别人的经验分享,终于找到一个关键词异步更新队列

文档中

查阅文档,果然没错Vue 异步执行 DOM 更新。

文档中这样解释:

只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会一次推入到队列中。

这本身是一个非常好的机制,因为 这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。

我们平时工作也不会去在意,也不关心 刷新队列时,组件会在事件循环队列清空时的下一个“tick”更新。

但是如果你想在 DOM 状态更新后做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员沿着“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们确实要这么做。

就像是上面我遇到的这种情况,不可避免必须要操作dom.这时 Vue.nextTick(callback)  就派上了用场

为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。例如:

//html
<div id="example">{{message}}</div>
//javascript
var vm = new Vue({
     el: '#example',
     data: {
         message: '123'
     }
})
vm.message = 'new message' // 更改数据
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
    vm.$el.textContent === 'new message' // true
})

在组件内使用 vm.$nextTick() 实例方法特别方便,因为它不需要全局 Vue ,并且回调函数中的 this 将自动绑定到当前的 Vue 实例上:

Vue.component('example', {
    template: '<span>{{ message }}</span>',
    data: function () {
        return {
            message: '没有更新'
        }
    },
    methods: {
        updateMessage: function () {
            this.message = '更新完成'
            console.log(this.$el.textContent) // => '没有更新'
            this.$nextTick(function () {
                 console.log(this.$el.textContent) // => '更新完成'
            })
        }
    }
})

根据这个思路,我将绘制图表的方法,放到 $nextTick的回调函数中执行

this.$nextTick(function () {//异步更新队列,回调函数在 DOM 更新完成后就会调用
     this.drawEchartsLine();//渲染图表
})

点击切换,再也没有报错,体验如丝般顺滑。

白俊遥博客
请先登录后发表评论
  • 最新评论
  • 总共0条评论