优化方案适用于首屏加载之类的,初始化出现白屏的情况,当一个工程庞大到一定程度的时候,不去做异步分割的话,会导致初始化越来越慢。Chrome的控制台里可以看到Dom渲染的时间。
我们新建一个入口页面,引入一个子组件,然后在template中使用。
<template>
<div class="costApplyIndexBox">
测试
<child></child>
</div>
</template>
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import child from "./childComponents.vue";
@Component({
components: {
child
},
mixins: []
})
export default class testIndex extends Vue{
created(){
}
}
</script>
这时我们去页面上面去查看,页面的加载情况,
页面上面显示只加载了一个js文件,且是我们的主入口文件,我们直接引入的child是被打包进主入口文件里面去了,跟着主入口一起被加载了。事实上,很多场景并不需要子组件和入口一起进行加载的。这个时候我们就需要对子组件进行拆分了。
什么是异步组件?顾名思义,他是一个异步加载的组件,并不是与入口组件同步加载的。
这样,异步组件的存在,可以使得我们按需使用与加载。我们应该如何使用异步组件?Vue提供了 defineAsyncComponent 方法供我们实现。
首先现在vue.config.js中配置一下分割的条件。
optimization:{
splitChunks:{
chunks: 'all', // 分割哪些类型的chunk,默认为'async',可选值有'all'、'async'、'initial'和function
minSize: 30000, // 单个chunk的最小体积,单位为字节(bytes),默认为30000(约30KB)
maxSize: 0, // 单个chunk的最大体积,0表示无限制,默认为0
maxAsyncRequests: 30, // 并行加载时最大允许的请求数量,默认为5
maxInitialRequests: 20, // 入口点加载时最大允许的请求数量,默认为3
automaticNameDelimiter: '~',//文件之间链接的符号
}
}
这里就不对splitChunks做过多的解释了,它实在是太有趣了,三言两语讲不清楚,只粗略的讲一下几个关键的参数。感兴趣的可以直接去webpack:详解代码分离以及插件SplitChunksPlugin的使用 中好好体会(强烈建议去观看)。
我们在刚刚的代码上引入 defineAsyncComponent ,将子组件通过它来引入。
<script lang="ts">
import {defineAsyncComponent} from 'vue'
import { Component, Vue } from "vue-property-decorator";
const child = defineAsyncComponent(() => import('./childComponents.vue'))
@Component({
components: {
child
},
mixins: []
})
export default class testIndex extends Vue{
created(){
}
}
</script>
如你所见,defineAsyncComponent 方法接收一个返回 Promise 的加载函数。这个 Promise 的 resolve
回调方法应该在从服务器获得组件定义时调用。你也可以调用 reject(reason)
表明加载失败。
ES 模块动态导入也会返回一个 Promise,所以多数情况下我们会将它和 defineAsyncComponent 搭配使用。类似 Vite 和 Webpack 这样的构建工具也支持此语法 (并且会将它们作为打包时的代码分割点),因此我们也可以用它来导入 Vue 单文件组件。
这时我们再返回页面上面查看JS的加载情况。
多了一个js文件,他是我们异步加载的子组件,我们是可以对其进行命名的,我们只需要在import中添加一行注释。
//webpackChunkName:'' 拆分的文件名,这将会影响到后面打包生成的文件
const child = defineAsyncComponent(() => import(/* webpackChunkName: 'page-child' */'./childComponents.vue'))
这时候子组件就拥有了自己的文件名。
这样体现不出异步加载的感觉,我们将其用v-if包裹起来,并且增加一个三秒后展示子组件的逻辑。
<template>
<div class="costApplyIndexBox">
测试
<child v-if="showChild"></child>
</div>
</template>
<script lang="ts">
import {defineAsyncComponent} from 'vue'
import { Component, Vue } from "vue-property-decorator";
const child = defineAsyncComponent(() => import(/* webpackChunkName: 'page-child' */'./childComponents.vue'))
@Component({
components: {
child
},
mixins: []
})
export default class testIndex extends Vue{
private showChild:boolean = false
created(){
setTimeout(()=>{
this.showChild = true;
},3000)
}
}
</script>
再次回到页面上去,页面初始化并没有加载我们的“page-child”。
三秒之后,子组件展示,并执行“page-child”js文件,这时我们异步组件就拆分完成了。
接下来讲一些使用方法的拓展。异步组件再被引入之后他同样是一个组件对象,是可以被component标签识别的。这样做的好处就是无需在components中注册它。
<template>
<div class="costApplyIndexBox">
测试
<component :is="child"></component>
</div>
</template>
<script lang="ts">
import {defineAsyncComponent} from 'vue'
import { Component, Vue } from "vue-property-decorator";
@Component({
components: {
// child
},
mixins: []
})
export default class testIndex extends Vue{
private showChild:boolean = false
private child:any = ''
created(){
setTimeout(()=>{
this.child = defineAsyncComponent(() => import(/* webpackChunkName: 'page-child' */'./childComponents.vue'))
this.showChild = true;
},3000)
}
}
</script>
在setup中甚至无需使用component标签,直接可以在template中使用它。
<template>
<div class="costApplyIndexBox">
测试
<component :is="child"></component>
<child></child>
</div>
</template>
<script setup lang="ts">
import { defineAsyncComponent,onMounted} from "vue";
// import { Component, Vue } from "vue-property-decorator";
const child = defineAsyncComponent(() => import(/* webpackChunkName: 'page-child' */'./childComponents.vue'))
onMounted(() => {
console.log(child);
})
</script>
这里要注意的是 defineAsyncComponent 引入的组件,并非为全局组件,它的作用域仅在当前组件内部,并不是说不需要再components中注册就变成了全局组件。
当然他可以被注册为全局组件。
<template>
<div class="costApplyIndexBox">
测试
<component :is="child"></component>
<child></child>
</div>
</template>
<script lang="ts">
import {defineAsyncComponent} from 'vue'
import { Component, Vue } from "vue-property-decorator";
@Component({
components: {
// child
},
mixins: []
})
export default class testIndex extends Vue{
private showChild:boolean = false
private child:any = ''
created(){
setTimeout(()=>{
this.child = defineAsyncComponent(() => import(/* webpackChunkName: 'page-child' */'./childComponents.vue'))
Vue.component('child',this.child)
this.showChild = true;
},3000)
}
}
</script>
使用 Vue.component('组件名',组件对象) 即可挂成为全局组件。这种方法尽量少挂,或者不挂,保不齐会出现组件名重复冲突的情况等等。不想每个组件都写一遍import,同样可以建一个组件仓库的ts文件,在里面提前引入好,只用import一个文件同样可以使用。
componentsHome.ts
export default {
child:() => import(/* webpackChunkName: 'page-child' */'./childComponents.vue')
}
这里没有使用 defineAsyncComponent 。是因为我发现import返回的也是一个组件对象。
在我翻阅完了 defineAsyncComponent 的源码的时候,发现了它最后return了一个对象。
return function() {
return {
component: f(),
delay: i,
timeout: c,
error: r,
loading: n
}
}
这里的 f() 事实上就是我们传入的 import() 函数。
loader
: 用于加载组件的函数,返回一个Promise。loadingComponent
: 加载中显示的组件。errorComponent
: 加载失败时显示的组件。delay
: 开始显示加载指示器前的延迟时间。timeout
: 组件加载的超时时间,超过此时间则视为加载失败。
它提供了更细致化的配置,可以使我们的异步组件更加完善。同理无法使用defineAsyncComponent 的低版本Vue 可以手动封装这样的一个对象去使用。
以上只是本人对于异步组件的一些理解,有错误的地方的话,欢迎大家评论区交流与指点。