1.背景
异步组件的概念最早在vue2就已经提出,而在vue3中因为增加了函数式编程,因此更加完整。 1.什么是异步组件? 在解释异步组件之前,我们需要先来了解一下webpack拆包的几种方式:async(异步)、initial(同步)、all【对应的配置属性为SplitChunk】 (所谓拆包就是,将什么样的文件的代码进行合并到一个js文件中,如果不拆包那么所有的文件都合并到一个js文件中,那么这个js文件会很大)
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'async', //三选一:"initial" 初始化,"all"(默认就是all),"async"(动态加载)
minSize: 30000, // 形成一个新代码块最小的体积,只有 >= minSize 的bundle会被拆分出来
maxSize: 0, //拆分之前最大的数值,默认为0,即不做限制
minChunks: 1, //引入次数,如果为2 那么一个资源最少被引用两次才可以被拆分出来
maxAsyncRequests: 5,// 按需加载的最大并行请求数
maxInitialRequests: 3, // 一个入口最大并行请求数
automaticNameDelimiter: '~', // 文件名的连接符
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
};
其中all是默认的,而我们这里要讨论的是splitchunk值为async的时候,即按照异步方式拆包,意味着模块只要是异步方式导入的就需要单独拆出来,那webpack怎么识别它是异步导入的呢?在vue2中使用import()函数引入的vue组件。那么这个组件就是异步组件,拆包时会被拆除单独的包。
2.vue2中的异步组件
使用方式:import()函数是重点标识
<template>
<component :is="myComponent"></component>
<button @click="switchCmp">切换组件</button>
</template>
<script>
export default {
components: {
'myComponent1': () => import('./my-component1'), // 引入方式二
'myComponent2': () => import('./my-component2'),
},
data() {
return {
myComponent: myComponent1
}
},
methods: {
switchCmp() {
this.myComponent = myComponent2;
}
}
}
</script>
3.vue3中的异步组件
vue3中不再使用import函数了,而是使用defineAsyncComponent()定义异步组件函数api,专门用来定义异步组件。
<template>
<div>
<h1>App</h1>
<hr />
<Component1 />
<asyncComponent />
<asyncComponentOptions />
</div>
</template>
<script setup>
import ErrorPage from "@/components/ErrorPage.vue";
import LoadingPage from "@/components/LoadingPage.vue";
import { defineAsyncComponent } from "vue";
// 同步写法
import Component1 from "@/components/Component1.vue";
// 异步写法
const asyncComponent = defineAsyncComponent(() =>
import("@/components/Component1.vue")
);
// const asyncComponent = () => import('@/components/Component1.vue') // vue2 写法
// 带配置项异步写法
const asyncComponentOptions = defineAsyncComponent({
loader: () => import("@/components/Component1.vue"),
delay: 300,
timeout: 5000,
errorComponent: ErrorPage,
loadingComponent: LoadingPage,
});
</script>
4.动态组件和异步组件的区别
<template>
<component :is="componentKey" ref="custom"></component>
</template>
import { reactive, ref, shallowReactive, onActivated, defineAsyncComponent} from 'vue';
const componentKey = ref(null);
const components: any = shallowReactive({});
// customModal得到的是包装过的异步组件
const customModal = defineAsyncComponent(() => import('./modal/CustomModal.vue'));
componentKey = customModal
业务中使用: 点击弹窗开关,然后通过cpPath路径判断是否存在对应的id,如果不存在,那么说明没有加载过这个异步组件,此时需要使用inject,拿到modelRegister对应的函数,然后执行:
<component
v-for="dialog in dialogCompsMap"
:is="dialog[1].comp"
:key="dialog[0]"
v-bind="dialog[1].props"
v-on="dialog[1].onEmits"
/>
provide(modelRegister, (cpPath: string) => new Promise(resolve => {
openGlobalLoading()
const uuid = UUID() // 获取一个uuid
dialogCompsMap.value.set(uuid, { // 所有的uuid生成后都存在一个Map中,生成key-对象
comp: defineAsyncComponent( // 对象中【comp属性】存的就是defineAsyncComponent返回的组件
() => import(`~/components/Common/Dialog/${cpPath}.vue`).then(res => {
resolve(uuid)
// 这里通过resolve(uuid),这样后面可以通过inject('modelRegister'), 执行await 函数拿到uuid,就可以通过uuid拿到map中的对象,进而得到comp组件。
return res // import的组件值还是原封不动返回
}).catch(() => {
message.error('资源有更新,请刷新页面后重试!')
}).finally(() => {
closeGlobalLoading()
}),
),
props: {},
onEmits: {},
})
triggerRef(dialogCompsMap)
}))
provide(modelUnInstall, (uuid: string) => { // 在dialog组件的unmounted的时候
if (dialogCompsMap.value.has(uuid)) {
dialogCompsMap.value.delete(uuid)
}
triggerRef(dialogCompsMap)
})
// 使用弹窗
<a-button type="link" @click="viewAuditStepHandle">
{{ formNum ? "查看" : "模拟" }}审批链
</a-button>
// 引入动态加载弹窗组件hooks
const { getModelUUID, openModel } = useDynamicDialog()
const viewAuditStepHandle = async () => {
const uuid = await getModelUUID('AuditStepDialog/AuditStepDialog') // 获取uuid
// 通过 openModel打开弹窗
openModel(uuid, {
getStepRequest: () => getFormProcessList(),
})
}
import { inject, onUnmounted } from 'vue'
import {
modelOpen,
modelRegister,
modelUnInstall,
} from '~/config/symbolVariable.config'
export function useDynamicDialog() {
const provideModelRegister = inject<(...args: any[]) => string | string[]>(
modelRegister) as (...args: any[]) => string
const provideModelOpen = inject<(...args: any[]) => string | string[]>(
modelOpen) as (...args: any[]) => string
const provideModelUnInstall = inject<(...args: any[]) => string | string[]>(
modelUnInstall) as (...args: any[]) => string
const modelMap = new Map<string, string>()
async function getModelUUID(cpPath: string) {
let modelUUID = modelMap.get(cpPath)
if (!modelUUID) {
const uuid = await provideModelRegister(cpPath)
console.log('uuid', uuid)
modelUUID = uuid
modelMap.set(cpPath, modelUUID)
}
console.log('modelUUID', modelUUID)
return modelUUID
}
function openModel(uuid: string, props = {}, onEmits = {}) {
provideModelOpen(uuid, props, onEmits)
}
onUnmounted(() => {
provideModelUnInstall(modelMap.values())
})
return {
getModelUUID,
openModel,
}
}