一. 如何定义异步组件
定义异步组件需要使用vue提供的 defineAsyncComponent()
方法:
注意:通过观察大家也可以发现,所谓的异步组件就相当于给普通组件套了一层外壳(defineAsyncComponent()
),当然现在还不能感受到异步组件的魅力,不着急,咱们继续学习。
二. 如何注册异步组件(两种注册方式):
1. 局部注册
在1.1中所使用的方式就是局部注册,跟普通组件的局部注册是一样的。
2. 全局注册
全局注册方式就是在 main.js
中使用 app.component(组件名,异步组件)
的方式进行注册。
现在大家去任何组件中使用 <AsyncBtn></AsyncBtn>
都能正常使用。
三. 什么是异步组件
通过 defineAsyncComponent()
方法包括的普通组件就是异步组件,这么解释太过于肤浅,让咱们通过一个现象来感受一下异步组件。
1. 通过普通方式导入组件和通过 defineAsyncComponent()
方法导入组件,但是不使用,查看控制台中 network
选项下组件的表现方式
① 将 <AsyncBtn></AsyncBtn>
组件以普通方式导入不使用,查看 network
大家可以看到,虽然 <AsyncBtn></AsyncBtn>
组件没有使用,但是,同样会被加载进来。
② 将 <AsyncBtn></AsyncBtn>
组件以 defineAsyncComponent()
方法导入不使用,查看 network
大家可以看到,异步组件在没有使用的时候不会被加载。
2. 通过普通方式导入组件和通过 defineAsyncComponent()
方法导入组件,但是不使用,对项目进行打包观察打包结果。
① 将 <AsyncBtn></AsyncBtn>
组件以普通方式导入,使用一下组件,打包项目观察打包结果。
大家可以看到,about
组件只打包出来了一个 AboutView-178...
文件,说明普通方式导入的组件也被打包进了 AboutView-178...
文件。
② 将 <AsyncBtn></AsyncBtn>
组件以 defineAsyncComponent()
方法导入,使用一下组件,打包项目观察打包结果。
大家可以看到,异步组件被单独打包成了一个独立文件。
3. 总结
根据以上两个现象,咱们可以总结一下什么是异步组件了。
在 Vue 中,当我们注册全局或局部组件时,它们都是同步地被“立即解析并加载”的。这意味着在我们的程序初始化时,所有组件都会通过网络被下载到内存中,并且在内存中占用一定的资源。预加载所有组件会增加页面的初始加载时间和性能降低。
而异步组件可以将我们的组件分开打包,按需要加载,这样可以减轻初始页面加载时间和减少资源浪费。当我们需要和路由配合使用时,异步组件也能帮助我们实现按需加载和动态导入。目的就是降低项目的内存占用,可以从一定程度上解决首页白屏问题。
四. 利用异步组件优化性能的案例
将来你首页的内容有很多,但是你的屏幕大小是固定的,也就是用户第一眼就只能看到第一屏的内容,之后的内容需要滚动滚动条才能看到,那么,咱们可以根据此特性,将屏幕外的内容做成动态组件,只有用户滚动到异步组件位置的时候,才加载异步组件。
大家将 AboutView
组件做成如下样式,AboutView
组件的样式可以是 100vh
,然后让他的内容块高一些,产生纵向的滚动条,模拟首页内容比较多的情况。
1. 想要完成接下来的效果,咱们要借助一个插件 -- @vueuse/core
,他提供了一个方法 useIntersectionObserver
,可以监听某个 DOM节点是否进入了视口
① 安装插件:npm i @vueuse/core -S
② 导入 useIntersectionObserver()
方法,并使用此方法监听某个DOM是否进入了视口 如:import { useIntersectionObserver } from '@vueuse/core' /* * target: 要监听的DOM节点 * params: 只包含一个元素的数组参数,这个唯一的元素中有一个属性:isIntersecting * isIntersecting: 此属性的值是true/false,表明当前DOM节点是否进入了视口 */ useIntersectionObserver(target, (params) => { console.log(params) })
2. 完成此案例
① 此案例的核心思想就是:在区域4放置一个异步组件,通过 v-if
指令绑定一个默认值为 false
的响应式数据;然后使用 useIntersectionObserver()
方法监听区域4是否进入视口,当进入视口时,将 v-if
绑定的响应式数据的值改为 true
;
如:<template>
<div class="about">
<h1>This is an about page</h1>
<div class="section1">内容区域1</div>
<div class="section2">内容区域2</div>
<div class="section3">内容区域3</div>
<div class="section4" ref="target">
内容区域4
<br />
<AsyncBtn v-if="isShow"></AsyncBtn>
</div>
</div>
</template>
<script setup>
// 导入监听方法
import { useIntersectionObserver } from '@vueuse/core'
// 导入ref
import { ref } from 'vue'
// 导入定义异步组件的方法
import { defineAsyncComponent } from 'vue'
// 定义异步组件
const AsyncBtn = defineAsyncComponent(() => {
return import('@/components/async/AsyncButton.vue')
})
// 获取区域4 DOM节点
const target = ref(null)
// 控制 <AsyncBtn></AsyncBtn> 是否显示的变量
const isShow = ref(false)
// 监听区域四是否进入视口
useIntersectionObserver(target, ([{ isIntersecting }]) => {
if (isIntersecting) {
// 这里为什么不直接让 isShow.value = isIntersecting;
// 因为 isIntersecting 会随着滚动在true和false之间切换,如果按以上方式赋值,那么<AsyncBtn></AsyncBtn>组件会被反复销毁重建,显然性能上肯定不够优秀。
isShow.value = true
}
})
</script>
② 思考
如果将来你不知道从哪一部分开始将你的项目的组件做成按需加载,比如当前案例中的内容区域2。但其实你完全不用担心,你完全可以把第一部分除外的其他部分根据你的需求做成按需加载,因为 useIntersectionObserver()
方法不仅会再DOM节点出现时执行,在DOM节点不出现是也会执行,所以你完全可以根据 isIntersecting
属性的true和false控制异步组件的显示与否。
五. <Suspense>
组件
1. <Suspense>
组件是用来跟异步组件一起使用的,他主要是用来处理当异步组件中导入的组件一直导入不成功时,定制一个默认显示内容。
2. 什么情况下异步组件中导入的组件会不成功呢?
异步组件中导入的普通组件能被导入成功的前提是普通组件的生命周期执行完毕,也就是组件创建好了。如果生命周期没有执行完毕,那么 import()
方法就无法将组件导入!
3. 但是,一个组件的生命周期执行是非常迅速的,那么,什么情况下组件的生命周期会被阻断?
要想理解什么情况下生命周期会被组件,大家要知道一个知识点,就是当你通过setup语法糖(<script setup>
)写组件逻辑时,<script setup></script>
标签内部就相当于是一个 async
函数的函数体。所以,在他里边就可以使用 await
关键词阻断组件的生命周期了。
如: <script setup>
let res = await new Promise((resolve) => {
setTimeout(() => {
resolve('hello,生命周期被阻断了5秒钟')
}, 5000)
})
console.log(res)
</script>