Vue 异步事件更新 & 异步Dom更新解决方案

Posted 小章鱼哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue 异步事件更新 & 异步Dom更新解决方案相关的知识,希望对你有一定的参考价值。

Vue 异步事更新件 & 异步Dom更新解决方案

1. Question

最近遇到一个有点别扭的需求。

进入一个子页面,需要四个接口拿到四份数据【a,b,c,d】
其中a,b正巧父级拿到过,在父级页面上用v-if指令保证ab存在,再去展示子页面。

现在,cd必须在子页面拿到了。c依赖浏览器的query pidd依赖c的一个字段c.pgid

画一个图,大概是这样的需求:

然后我的旧实现思路是这样的:

// using typescript

  private created() 
  // 请求c后立刻请求d
   this.fetchC().then(this.fetchD);
  

  // 后台请求c
  private fetchC() 
   this.$store.dispatch('resource/c', this.pid);
  

  // 从store获取c
  private get c() 
    return this.$store.getters['resource/c'](this.pid);
  

  private get pid(): number 
    return Number(this.$route.query.pid);
  

  private get pgid() 
    if (!this.c) 
      return null;
    

    return this.c.pgid;
  

// 后台请求d
  private fetchD() 
    if (!this.pgid) 
      return;
    

    return this.$store.dispatch('resource/d', this.pgid);
  

 // store拿d
  private get d() 
    return this.$store.getters['resource/d'](this.pgid);
  

dispacth c之后立即then一下dispatch d。这个时候,get c()并不能保证一定已经get到,this.pgid更不一定能拿到,dispatch d方法就会报错。

当时有点蒙圈,其实很简单一个事,只要保证this.pgid存在的时候再去dispatch d就可以了。

2. Action

2.1 正确姿势

稍微改一点点代码:

// using typescript

  private created() 
  // 请求c后不去立刻请求d了
   this.fetchC();    //  此行改了
  

  // 后台请求c
  private fetchC() 
   this.$store.dispatch('resource/c', this.pid);
  

  // 从store获取c
  private get c() 
    return this.$store.getters['resource/c'](this.pid);
  

  private get pid(): number 
    return Number(this.$route.query.pid);
  

  private get pgid() 
    if (!this.c) 
      return null;
    

    return this.c.pgid;
  

// 后台请求d,通过监听pgid的值,存在之后再去fetchD
  @Watch('pgid')    // 此行改了
  private fetchD() 
    if (!this.pgid) 
      return;
    

    return this.$store.dispatch('resource/d', this.pgid);
  

 // store拿d
  private get d() 
    if (!this.pgid)  return;   // 此行改了
    return this.$store.getters['resource/d'](this.pgid);
  

跑起来,一切都很完美。两个接口的数据都拿到了。

这是一个思路,把重心放在数据有没有拿到上,用watch监听,等数据拿到之后,再去发送第二个请求。

但是有点慌啊,这一块感觉还是有点迷啊。

我如果不把思路放在数据上,我就去控制请求(感觉这样子更符合开发的思路,代码更容易理解),有没有哪个生命周期的时刻,第一个请求回来,get的第一个数据也成功拿到,在这个生命周期时刻,第二个请求才开始呢?

第二个请求放在$nextTick里面行不行?
不论dispatch方法的快慢问题,到底getdispatch谁先执行?有没有一个先后顺序的?

2.2 一个试验

settimeout当作dispatch测试一下。

<html>

<head>
  <title>test</title>
</head>

<body>
  <div id="app">
    <span>c</span>
    <span>d</span>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue"></script>
  <script>
    var app = new Vue(
      el: '#app',
      data: 
        pgid : 1
      ,
      computed: 
        c: function () 
          if (!this.pgid) 
            return ;
          
          console.log('c computed ' + this.pgid);
          return ++this.pgid;
        ,
        d: function () 
          if (!this.c) 
            return ;
          
          console.log('d computed ' + this.c);
          return ++this.c;
        
      ,
      created: function () 
         // 模拟c的请求,这里我不知道这个请求返回需要多少秒,假设1s
        setTimeout(() =>    
          console.log('time out');
          this.pgid = 100;
        , 1000);
        this.$nextTick(() => 
          console.log('next tick');
          this.pgid = 10;
        )
        console.log('created');
      ,
      mounted: function () 
        console.log('mounted');
      
    );
  </script>
</body>
</html>

运行结果:

有图有真相了,看图学习:

官方对$nextTick的解释是:

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

官方对created方法的解释是:

在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。

官方对mounted方法的解释是:

el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。

注意 mounted 不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用 vm.$nextTick 替换掉 mounted:

mounted: function () 
  this.$nextTick(function () 
    // Code that will run only after the
    // entire view has been rendered
  )

哦,也就是说在vue的created方法结束之后,mounted方法开始,紧接着执行$nextTick这个方法。

呸,我不信!

我把延迟改成0,我的请求巨快!

created: function () 
        // 我把这个改成0
        setTimeout(() => 
          this.pgid = 100;
          console.log('time out');
        , 0);
        this.$nextTick(() => 
          console.log('next tick');
          this.pgid = 10;
        )
        console.log('created');
      

结果:

好吧,并没有什么变化,还是$nextTick快。

现在,起码明白了一个事: 遇到异步事件,你用拦截异步事件的思路是不通的,mountednextTick都拦不住,用settimeout延时你又不知道异步事件的延迟具体是多少秒。实际上异步事件执行的时间更靠后。而且远远晚于dom创建和首次展示的时间。

所以两个请求在created方法时就前后发出,第二个请求又依赖第一个请求的参数,这样的姿势肯定是不正确了。

2.3 还有困惑

get一定是在created方法之后?

并不是。。。。

getdispatch方法谁先执行???比如这个get dfetch d 都依赖pgidpgid一变,谁先变??也不一定。。。。

老老实实的用数据控制一切吧。

参考:
https://juejin.im/post/5a6fdb846fb9a01cc0268618

以上是关于Vue 异步事件更新 & 异步Dom更新解决方案的主要内容,如果未能解决你的问题,请参考以下文章

异步更新队列 nextTick

初识vue 2.0(12):使用$nextTick获取更新后的DOM

[Vue] vue的一些面试题4

vue nextTick深入理解-vue性能优化DOM更新时机事件循环机制

Swiper和Vue配合使用的问题——Vue的异步更新DOM

Vue中的nextTick