前言

曾经在百度上搜这个问题,看到的答案都是相互复制粘贴的,而且个人觉得并没具体场景举例说明,并没有好的答案。于是自己立贴,留下这个坑,等日后遇到了一些场景,就把分析写在这里,把这个问题搞明白。

经过一些网友的评论提醒,发现之前写的结论还是过于草率,本人也会不断的更新完善。感谢。

前置知识点:

  • 事件循环机制
  • 子父组件的生命周期执行顺序

这两个知识点最好要有所了解,这样下面能够更好的了解原理。


两个生命周期

created:data属性,methods属性,watch监听等都初始化好了,也就是可以使用了。

mounted:已经把内存中编译好的模板替换到页面中,也就是视图层已渲染成最新的,vue 实例完全创建完毕。可以操作DOM。理论上内部的子组件也执行完了mounted。


场景1

之前看到网上最多复制粘贴的是:

建议放在created里,如果在mounted钩子函数中请求数据可能导致页面闪屏问题。其实就是加载时机问题,放在created里会比mounted触发早一点,如果在页面挂载完之前请求完成的话就不会看到闪屏了。

下面有网友说了闪屏是什么意思,就是在请求没回来之前,页面已经挂载好了,显示的是data里的原始数据(或者空数据),当请求回来后替换掉data的内容,就会有个内容切换的情况出现,也就是闪屏。

但上面说的放在created不是解决这个问题的关键,因为异步请求是异步执行,vue会先将created和mounted里的同步代码先执行完,才会开启下一轮的事件循环去执行这俩钩子里异步请求的方法。

口说无凭,上代码:

<div v-for="i in 20000" :key="i"> // 循环2w张图,为了让mounted晚执行
  <img src="../public/test.png" alt="" srcset="" />
</div>
created() {
 this.getData(
    " https://mock.mengxuegu.com/mock/625bfd5d66abf914b1f1bf07/example/proxy"
  );
  console.log("created执行", new Date());
},
mounted() {
  console.log("mounted执行", new Date());
},
methods: {
  getData(url) {
    fetch(url).then(
      (response) => {
        //处理http响应
        console.log("请求到数据", new Date());
      },
      (error) => {
        //处理错误
        console.log("请求到数据", error);
      }
    );
  },
},

运行后看看打印台:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

咱们可以看到,created是在18秒的时候执行的,mounted是在20秒执行的,咱们这个异步请求也是在20秒发送,获取到数据的时间是0.1秒。如果按照网上的说法,那么应该是created打印完,0.1秒后请求的结果打印,最后不到2秒后mounted打印。但事实是异步请求在mounted之后。

所以,不存在说异步请求放在created里会比mounted触发早一点的情况,此时的异步请求无论放在哪个钩子里,都是会在mounted之后执行。

这里还有一个补充,假如有ab两个请求一前一后相继触发,b是1秒后拿到数据,a是3秒后拿到数据,b的then会先执行。

场景2

这个也是网上复制粘贴比较多的。

如果把所有请求放在created里面的话,请求过多会,加载太慢会导致页面出现短暂的白屏情况,一般上我写的话,接口不复杂会放created里面,接口多复杂的话会放在mounted里面。

其实这个描述已经有点接近意思了,我们用一个拥有子组件的父组件来举例子,然后来分两种情况去看。

第一种

该父组件内的子组件不需要在内部钩子中调用接口请求。那么就会和场景一一样,父组件先执行完mounted的同步代码后,才去调用异步请求,此时组件已挂载完毕。多少个异步请求放在哪个钩子都一样,都是会加载完子组件后才去请求。

所以一定会先白屏,放哪都一样。

第二种

该父组件内部有一个子组件在钩子中调了接口请求。我们知道理论上,父组件在执行完beforeMounted后就会去启动子组件的生命周期,子组件执行完mounted之后,父组件才会执行自己的mounted。

当我们在父组件中把异步请求放在created中时:

父:

created() {
  this.getData(
    " https://mock.mengxuegu.com/mock/625bfd5d66abf914b1f1bf07/example/proxy"
  );
  console.log("created执行", new Date());
},
mounted() {
  console.log("mounted执行", new Date());
},
methods: {
  getData(url) {
    fetch(url).then(
      (response) => {
        //处理http响应
        console.log("父请求到数据" + url, new Date());
      },
      (error) => {
        //处理错误
        console.log("请求到数据", error);
      }
    );
  },
},

子:

mounted() {
  this.getData(
    " https://mock.mengxuegu.com/mock/625bfd5d66abf914b1f1bf07/example/query"
  );
  console.log("子mounted执行", new Date());
},
methods: {
  getData(url) {
    fetch(url).then(
      (response) => {
        //处理http响应
        console.log("子请求到数据", new Date());
      },
      (error) => {
        //处理错误
        console.log("子请求到数据", error);
      }
    );
  },
},

此时执行,在控制台能看到两种情况:
在这里插入图片描述
在这里插入图片描述
能看到父子谁先拿到数据谁就打印,但接口的触发顺序应该是子先。

好,接下来让父级渲染2w张图,结果又是这样:

在这里插入图片描述
在这里插入图片描述

明明子的数据先拿到,但却先打印父的数据,且把20w张图换到子组件去渲染也是一样的情况。

原因我还不太明确,但是是不是可以说当页面渲染稍微多点时,把异步请求放在created中时,父总是会先触发then呢,无论子多快拿到数据。

ok,我们这回把父的异步请求放进mounted中执行,当子组件渲染2w张图和当父组件渲染2w张图的结果都类似以下的例子:

在这里插入图片描述
在这里插入图片描述
原因我还不太明确,但是是不是可以说当页面渲染稍微多点时,把异步请求放在mounted中时,子总是会先触发then呢,无论父多快拿到数据。

欢迎讨论!


场景3

本人亲自遇到,不过和异步没有多大关系,但和生命周期有关,就先记录在这里吧。使用elementUI时,有些组件如果在mounted中直接赋值,组件已经渲染过一次,添加数据后组件是不会更显示的。放在created里就可以。


结论

如果是单单的一个父级组件,放哪里都无所谓。

但是如果涉及到了要控制子父组件先后显示正确内容的时候,就可以考虑下父组件的请求要放在哪个钩子里了。想要子组件先拿到数据渲染就放在mounted中,想要父组件先拿到数据就放在created中。

没想到直接颠覆了我之前的结论,笑死


如何处理场景一和场景二的问题

其实都是同一个问题,白屏怎么办?

在没有拿到接口数据的时候如何显示页面,以及拿到数据后如何做体验比较友好的内容切换。

其实网上的解决方案有很多,我这里就抛砖引玉一下,例如第三方UI库的骨架屏组件,数据没来之前,显示骨架屏,数据到了做个切换体验会好些;又例如数据没来之前搞个全局的loading样式,数据来了显示并取消loading等。


最后

这里给新人再加个提示。

如果拿到的数据是通过标签注入渲染显示的,最好搞个外标签判断一下是否有值再去渲染,怕dom已经开始渲染但数据还没拿到,有些组件会直接报错。

<div v-if="dataList.length > 0">
   // 防止内部模板报错
</div>
Logo

前往低代码交流专区

更多推荐