卧槽,牛逼!vue3的组件竟然还能“暂停”渲染!
前言
有的时候我们想要从服务端拿到数据后
再去渲染一个组件,为了实现这个效果我们目前有几种实现方式:
将数据请求放到父组件去做,并且使用
v-if
控制拿到子组件后才去渲染子组件,然后将数据从父组件通过props
传给子组件。在子组件的
onMounted
中请求数据,并且使用v-if
在子组件的template
最外层进行控制,只有拿到数据后才渲染子组件中的内容。
上面这两种方案都有各自的缺点,不够完美。最理想的方案是将从服务端获取数据的逻辑放在子组件中,并且在获取数据的期间让子组件“暂停”
一下,先不去渲染,等到数据请求完成后再第一次去渲染子组件。
欧阳写了一本开源电子书vue3编译原理揭秘,看完这本书可以让你对vue编译的认知有质的提升。这本书初、中级前端能看懂,完全免费,只求一个star。
完美的解决方案
第一种方法的缺点是:子组件虽然拿到数据后才开始渲染,但是数据请求的逻辑却放到了父组件上面,我们期望所有的逻辑都封装在子组件内部。
第二种方法的缺点是:实际上是初始化时就渲染了一次子组件,此时我们还没从服务端拿到数据。所以不得不使用v-if
在template
的最外层控制,此时不渲染子组件中的内容。当从服务端拿到数据后再第二次渲染子组件,此时才将子组件中的内容渲染到页面上。这种方法明显子组件渲染了2次。
那么有没有一种完美的方案,从服务端获取数据的逻辑放在子组件中,并且在获取数据的期间让子组件“暂停”
一下,先不去渲染,等到数据请求完成后再第一次去渲染子组件呢?
答案是:当然可以,vue3的Suspense组件
+在setup顶层使用await获取数据
就能完美的实现这个需求!!!
两个不完美的例子
为了让你更直观的看到完美方案的牛逼,我们先来看看前面讲的两个不够完美的例子。
父组件中请求数据的例子
下面这个是父组件中请求数据的例子,父组件的代码如下:
<template>
<ChildDemo v-if="user" :user="user" />
<div v-else>
<p>loading...</p>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
import ChildDemo from "./Child.vue";
const user = ref(null);
async function fetchUser() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
name: "张三",
phone: "13800138000",
});
}, 2000);
});
}
onMounted(async () => {
user.value = await fetchUser();
});
</script>
子组件的代码如下:
<template>
<div>
<p>用户名:{{ user.name }}</p>
<p>手机号:{{ user.phone }}</p>
</div>
</template>
<script setup lang="ts">
const props = defineProps(["user"]);
</script>
这种方案我们将从服务端获取user
的逻辑全部放到了父组件中,并且使用props
将user
传递给子组件,并且在从服务端获取数据的期间显示一个loading的文案。
这样虽然实现了我们的需求但是将子组件获取user
的逻辑放到了父组件中,我们期望将这些逻辑全部封装在子组件中,所以这个方案并不完美。
子组件在onMounted中请求数据的例子
我们来看看第二种方案,父组件代码代码如下:
<template>
<ChildDemo />
</template>
<script setup lang="ts">
import ChildDemo from "./Child.vue";
</script>
子组件代码如下:
<template>
<div v-if="user">
<p>用户名:{{ user.name }}</p>
<p>手机号:{{ user.phone }}</p>
</div>
<div v-else>
<p>loading...</p>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
const user = ref(null);
async function fetchUser() {
// 使用setTimeout模拟从服务端获取数据
return new Promise((resolve) => {
setTimeout(() => {
resolve({
name: "张三",
phone: "13800138000",
});
}, 2000);
});
}
onMounted(async () => {
user.value = await fetchUser();
});
</script>
我们将数据请求放在了onMounted
中,初始化时会去第一次渲染子组件。此时user
的值还是null
,所以我们不得不在template
的最外层使用v-if="user"
控制此时不显示子组件的内容,在v-else
中去渲染loading文案。
当从服务端拿到数据后给响应式变量user
重新赋值,会触发页面重新渲染,此时会进行第二次渲染才将子组件的内容渲染到页面上。
从上面可以看到这种方案子组件明显渲染了两次,并且我们还将loading的显示逻辑写在子组件的内部,增加了子组件代码的复杂度。所以这种方案也并不完美。
最完美的方案就是在fetchUser
期间让子组件“暂停”渲染
,fallback
去渲染一个loading页面。并且这个loading的显示逻辑不需要封装在子组件中,在“暂停”渲染
期间自动
就能显示出来。等到从服务端请求数据完成后才开始渲染子组件,并且自动的卸载掉loading页面。
Suspense + await实现完美的例子
下面这个是官网对Suspense
的介绍:
<Suspense>
是一个内置组件,用来在组件树中协调对异步依赖的处理。它让我们可以在组件树上层等待下层的多个嵌套异步依赖项解析完成,并可以在等待时渲染一个加载状态。
上面的意思是Suspense
组件能够监听下面的异步子组件,在等待异步子组件完成渲染之前,可以去渲染一个loading的页面。
Suspense
组件支持两个插槽:#default
和 #fallback
。如果#default
插槽中有异步组件,那么就会先去渲染 #fallback
中的内容,等到异步组件加载完成后就会将#fallback
中的内容给干掉,改为将异步组件的内容渲染到页面上。
如果我们的子组件是一个异步组件,那么Suspense
不就可以帮我们实现想要的功能吖。
Suspense
可以在异步子组件的加载过程中使用 #fallback
插槽自动帮我们渲染一个加载中的loading,等到异步子组件加载完成后才会第一次去渲染子组件中的内容。
那么现在的问题是如何将我们的子组件变成异步子组件?
这个问题的答案其实vue官网就已经告诉我们了,如果一个组件的<script setup>
顶层使用了await
,那么这个组件就会变成一个异步组件。我们接下来只需要在子组件的顶层使用await去请求服务端数据就可以啦。
完美方案的父组件
下面这个是使用Suspense
改造后的父组件代码,如下:
<template>
<Suspense>
<AsyncChildDemo />
<template #fallback>loading...</template>
</Suspense>
</template>
<script setup lang="ts">
import AsyncChildDemo from "./AsyncChild.vue";
</script>
在父组件中使用了Suspense
组件,给这个组件传了2个插槽。#default
插槽为异步子组件AsyncChildDemo
,默认插槽可以不用给元素上面添加#default
。
并且使用了#fallback
插槽,在异步子组件加载过程中会暂时先不去渲染异步子组件AsyncChildDemo
。改为先渲染#fallback
插槽中的loading,等到异步子组件加载完成后会自动将loading替换为子组件中的内容。
完美方案的子组件
下面这个是使用了await
改造后的子组件代码,如下:
<template>
<div>
<p>用户名:{{ user.name }}</p>
<p>手机号:{{ user.phone }}</p>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
const user = ref(null);
user.value = await fetchUser();
async function fetchUser() {
// 使用setTimeout模拟从服务端获取数据
return new Promise((resolve) => {
setTimeout(() => {
resolve({
name: "张三",
phone: "13800138000",
});
}, 2000);
});
}
</script>
我们在<script setup>
顶层中使用了await
,然后将await
拿到的值赋值给user
变量。在顶层使用了await
后子组件就变成了一个异步组件,等到await fetchUser()
执行完了后,也就是从服务端拿到了数据后,子组件才算是加载完成了。
并且由于我们在父组件中使用了Suspense
,所以在子组件加载完成之前,也就是从服务端拿到数据之前,都不会去渲染子组件(相当于“暂停”渲染子组件)。而是去渲染#fallback
插槽中的loading,等到从服务端拿到数据之后异步子组件才算是加载完成了。此时才会第一次去渲染子组件,并且将loading替换为子组件渲染的内容。
因为第一次渲染子组件时已经从服务端拿到了user
的值,此时user
已经不是null
了,所以我们可以不用在template的最上层使用v-if="user"
,尽管在template中有去读user.name
。
经过父组件Suspense + 子组件顶层await
的改造后,在渲染父组件的Suspense
时发现他的子组件有异步组件,就会“暂停”渲染子组件,改为自动渲染loading组件。
子组件在setup
顶层使用await
等待从服务端请求数据,当从服务端拿到了数据后此时子组件才算是加载完成,此时才会进行第一次渲染,并且自动将loading中的内容替换为子组件中渲染的内容。
并且在Suspense
中还支持多个异步子组件分别从服务端获取数据,等这几个异步子组件都从服务端获取到数据后才会自动的将loading替换为这几个异步子组件渲染的内容。
还有就是Suspense
组件目前依然还是实验性
的功能,生产环境使用需要谨慎。
简单看看Suspense
如何实现“暂停”渲染?
Suspense
在渲染子组件时,发现子组件是一个异步组件就不会立即执行异步子组件的render函数。而是会加一个名为deps
的标记,标明当前默认子组件是一个异步组件,暂停渲染
异步子组件。
由于异步子组件是一个Promise
,所以可以在加载异步子组件的Promise
后添加.then()
方法,在.then()
方法中才会去继续渲染异步子组件。
目前异步子组件已经暂停渲染了,接着就是会去读取deps
标记。如果deps
标记为true
,说明异步子组件暂停渲染了,此时就会去将fallback
插槽中的loading组件渲染到页面上。
当异步子组件加载完成后就会触发Promise
的.then()
方法,从而继续渲染
异步子组件。在.then()
方法中会去执行异步子组件的render函数去生成虚拟DOM,然后根据虚拟DOM生成真实DOM。最后就是将原本页面上渲染的fallback
插槽中的内容替换为异步组件生成的真实DOM中的内容。
下面这个是我画的流程图(流程图后面还有文末总结):
总结
这篇文章我们讲了有的场景需要从服务端拿到数据后
再去渲染一个组件,此时我们就可以使用父组件Suspense + 子组件顶层await
的完美方案。
在渲染父组件的Suspense
组件时发现他的子组件有异步组件,就会“暂停”渲染子组件,改为自动渲染loading组件。
子组件在setup
顶层使用await
等待从服务端请求数据,当从服务端拿到了数据后此时子组件才算是加载完成,此时才会进行第一次渲染,并且自动将loading中的内容替换为子组件中渲染的内容。
并且在Suspense
中还支持多个异步子组件分别从服务端获取数据,等这几个异步子组件都从服务端获取到数据后才会自动的将loading替换为这几个异步子组件渲染的内容。
最后就是Suspense
组件目前依然还是实验性
的功能,生产环境使用需要谨慎。
关注公众号:【前端欧阳】,给自己一个进阶vue的机会
另外欧阳写了一本开源电子书vue3编译原理揭秘,看完这本书可以让你对vue编译的认知有质的提升。这本书初、中级前端能看懂,完全免费,只求一个star。
卧槽,牛逼!vue3的组件竟然还能“暂停”渲染!的更多相关文章
- 推荐几个牛逼的 IDEA 插件,还带动图!
阅读本文大概需要 2.3 分钟. 作者:纪莫, cnblogs.com/jimoer 这里只是推荐一下好用的插件,具体的使用方法不一一详细介绍. JRebel for IntelliJ 一款热部署插件 ...
- 【项目总结】:怎样做一个牛逼的Team leader?
随着ITOO高校云平台3.1项目的结束,我们各种各样的总结也被提上了日程. Java版本号的全部开发者和Donet版本号的全部开发者坐在一起进行了关于项目开发管理的头脑风暴,尽管我仅仅是Donet开发 ...
- 没必要看源码。。把文档学通就已经牛逼了(我们大多还是在应用层,还达不到研究的程度。附class与examples大全链接)
[学霸]深圳-鑫 2017/7/11 13:54:07只是学习怎么用QT的话,不用看源码.看帮助文档就很好要学习编码风格与思路,就看看源码 [学神]武汉-朝菌 2017/7/11 13:54:39没必 ...
- 科学家有了钱以后,真是挺吓人的——D.E.Shaw的牛逼人生
科学家有了钱以后,真是挺吓人的——D.E.Shaw的牛逼人生 黑科技,还是要提D.E.Shaw Research这个奇异的存在. 要讲这个黑科技,我们可能要扯远一点,先讲讲D.E. Shaw这个人是怎 ...
- UVA10200-Prime Time/HDU2161-Primes,例题讲解,牛逼的费马小定理和欧拉函数判素数。
10200 - Prime Time 此题极坑(本菜太弱),鉴定完毕,9遍过. 题意:很简单的求一个区间 ...
- 技术大佬:我去,你竟然还在用 try–catch-finally
二哥,你之前那篇 我去 switch的文章也特么太有趣了,读完后意犹未尽啊,要不要再写一篇啊?虽然用的是 Java 13 的语法,对旧版本不太友好.但谁能保证 Java 不会再来一次重大更新呢,就像 ...
- 【转载】可能是世界上最牛逼的网站统计程序——Matomo
大家做网站的时候一般都会使用网站统计程序.通常,国内网站会使用百度统计.CNZZ等,而国外网站则会使用Google Analytics等统计.国内的统计程序普遍功能不太丰富,且响应速度一般.Googl ...
- 如何设计一个牛逼的API接口
在日常开发中,总会接触到各种接口.前后端数据传输接口,第三方业务平台接口.一个平台的前后端数据传输接口一般都会在内网环境下通信,而且会使用安全框架,所以安全性可以得到很好的保护.这篇文章重点讨论一下提 ...
- 为什么我会认为SAP是世界上最好用最牛逼的ERP系统,没有之一?
为什么我认为SAP是世界上最好用最牛逼的ERP系统,没有之一?玩过QAD.Tiptop.用友等产品,深深觉得SAP是贵的有道理! 一套好的ERP系统,不仅能够最大程度承接适配企业的管理和业务流程,在技 ...
- 我喜欢ASP.NET的MVC因为它牛逼的9大理由(转载)
我很早就关注ASP.NET的mvc的,因为最开始是学了Java的MVC,由于工作的原因一直在做.Net开发,最近的几个新项目我采用了MVC做了,我个一直都非常喜欢.Net的MVC.我们为什么使用MVC ...
随机推荐
- Oracle 三种分页方法
Oracle的三层分页指的是在进行分页查询时,使用三种不同的方式来实现分页效果,分别是使用ROWNUM.使用OFFSET和FETCH.使用ROW_NUMBER() OVER() 1.使用ROWNUM ...
- 【点云检测】OpenPCDet 教程系列 [1] 安装 与 ROS运行
前言与参考 主要是介绍库的使用,做笔记区 首先搜索的时候有个问题 一直在我脑子里 hhh 就是MMlab其实还有一个叫mmdetection3d 的库,然后搜的时候发现 hhh 有网友和我一样的疑惑: ...
- TI AM64x工业核心板规格书(双核ARM Cortex-A53 + 单/四核Cortex-R5F + 单核Cortex-M4F,主频1GHz)
1 核心板简介 创龙科技SOM-TL64x是一款基于TI Sitara系列AM64x双核ARM Cortex-A53 + 单/四核Cortex-R5F + 单核Cortex-M4F设计的多核工业级核心 ...
- yb课堂 课程总结
- 将传统应用带入浏览器的开源先锋「GitHub 热点速览」
现代浏览器已经不再是简单的浏览网页的工具,其潜能正在通过技术不断地被挖掘和扩展.得益于 WebAssembly 等技术的出现,让浏览器能够以接近原生的速度执行非 JavaScript 语言编写的程序, ...
- C# 使用模式匹配的好处,因为好用所以推荐~
类型检查和转换:当你需要检查对象是否为特定类型,并且希望在同一时间内将其转换为那个类型时,模式匹配提供了一种更简洁的方式来完成这一任务,避免了使用传统的as和is操作符后还需要进行额外的null检查. ...
- 领域驱动设计(DDD)分层架构的三种模式
模式一:四层架构 1.User Interface为用户界面层(或表示层),负责向用户显示信息和解释用户命令.这里指的用户可以是另一个计算机系统,不一定是使用用户界面的人.2.Application为 ...
- 在.NET Web API设置响应输出Json数据格式常用的两种方式
前言 在ASP.NET Core Web API中设置响应输出Json数据格式常用以下两种方式:可以通过添加System.Text.Json或Newtonsoft.JsonJSON序列化和反序列化库在 ...
- 洛谷[NOIP2015 普及组] 金币
[NOIP2015 普及组] 金币 题目背景 NOIP2015 普及组 T1 题目描述 国王将金币作为工资,发放给忠诚的骑士.第一天,骑士收到一枚金币:之后两天(第二天和第三天),每天收到两枚金币:之 ...
- 简单认识APP项目
manifests:里面只有一个xml,是app运行配置文件 清单文件 <?xml version="1.0" encoding="utf-8"?> ...