前言
问题起源于我们的一个页面,初始化加载资源过多,导致页面的Load时间过长
问题分析
- 页面由大量模块组成
- 所有模块是同时进行加载
- 每个模块的依赖资源较多(包括js文件,接口文件,css文件等)
应用场景
在项目中,在一个页面中有大量业务需要处理,我们可能需要拆分业务细化为更小的组件,并且仅在需要的时候再加载相关组件。
组件理解
component
一个用于渲染动态组件或元素的“元组件”,要渲染的实际组件由is决定
- 当
is是字符串,它既可以是 HTML 标签名也可以是组件的注册名。 - 或者,
is也可以直接绑定到组件的定义。
示例
<script setup>
import Foo from './Foo.vue'
import Bar from './Bar.vue'
</script>
<template>
// 渲染组件
<component :is="Math.random() > 0.5 ? Foo : Bar" />
// 渲染HTML
<component :is="href ? 'a' : 'span'"></component>
</template>
defineAsyncComponent
defineAsyncComponent 方法接收一个返回 Promise 的加载函数。
ES 模块动态导入也会返回一个 Promise,所以多数情况下我们会将它和 defineAsyncComponent 搭配使用。类似 Vite 和 Webpack 这样的构建工具也支持此语法 (并且会将它们作为打包时的代码分割点),因此我们也可以用它来导入 Vue 单文件组件:
示例
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
)
解决思路
- 业务组件化
- 将各模块拆分未组件模块,每个模块之间降低耦合
- 组件依赖的资源全部封装在组件内部进行调用
- 组件懒加载
- 优先加载可见模块
- 其余不可见模块懒加载,待需要时加载
实现原理
// 父组件
<template>
<!-- 动态组件 -->
<component v-if="currentComponentsName" :is="components[currentComponentsName]" ref="componentRef" @close="componentClose" />
</template>
<script setup lang="ts">
import { defineAsyncComponent } from "vue";
// 当前组件 Ref
const componentRef = ref();
// 当前组件名称
const currentComponentsName = ref("");
// 初始化组件传入的参数
let currentComponentsParams = {};
// 动态组件引入,使用时候才加载vue页面
const components = {
PatientRecallVue: defineAsyncComponent(() => import("./PIQComps/PatientRecall.vue")),
UrgentValueRecordVue: defineAsyncComponent(() => import("./PIQComps/UrgentValueRecord.vue")),
TransferTimeVue: defineAsyncComponent(() => import("./PIQComps/TransferTime.vue")),
QCBatchNoVue: defineAsyncComponent(() => import("./PIQComps/QCBatchNo.vue")),
EmergencyRelateVue: defineAsyncComponent(() => import("./PIQComps/EmergencyRelate.vue")),
DataBatchCopyVue: defineAsyncComponent(() => import("./PIQComps/DataBatchCopy.vue")),
ModifyReportRemarkVue: defineAsyncComponent(() => import("./PIQComps/ModifyReportRemark.vue")),
DelayedReportVue: defineAsyncComponent(() => import("./PIQComps/DelayedReport.vue")),
BatchAddCombinationsVue: defineAsyncComponent(() => import("./PIQComps/BatchAddCombinations.vue")),
SeparateCombineVue: defineAsyncComponent(() => import("./PIQComps/SeparateCombine.vue"))
};
// 解决弹框组件 重复触发当前组件失效问题
const componentClose = () => {
currentComponentsName.value = "";
componentRef.value = null;
};
// 确保动态组件渲染完成,再初始化数据,防止 Initialization 方法未找到
watch(
() => componentRef.value,
(newValue, oldValue) => {
/* ... */
if (!newValue) {
return;
}
componentRef.value.Initialization(currentComponentsParams);
}
);
</script>
// 子组件 PatientRecall.vue
<template>
<el-dialog
title="召回病人"
width="600px"
center
v-model="state.visible"
draggable
:close-on-click-modal="false"
destroy-on-close
@close="dialogClose"
>
<el-form class="mt10" label-width="80px">
<el-form-item label="姓名" prop="pidName">
<el-input v-model="state.ParentData.formData.pidName" readonly></el-input>
</el-form-item>
<el-form-item label="样本号" prop="repSid">
<el-input v-model="state.ParentData.formData.repSid" readonly></el-input>
</el-form-item>
<el-form-item label="召回原因" prop="obrValueD">
<el-input
type="textarea"
:autosize="{ minRows: 6, maxRows: 12 }"
v-model="state.ParentData.formData.obrValueD"
></el-input>
</el-form-item>
</el-form>
<el-tag class="mb10" effect="dark">召回记录</el-tag>
<el-table :data="state.ParentData.tableData" height="200px">
<el-table-column label="召回人" prop="obrSendUserName" align="center"></el-table-column>
<el-table-column label="召回时间" prop="obrCreateTime" align="center"></el-table-column>
<el-table-column label="召回内容" prop="obrValueD" align="center"></el-table-column>
</el-table>
<template #footer>
<el-button @click="cancelBtn">取消</el-button>
<el-button type="primary" @click="confirmBtn"> 确认 </el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ElMessage } from "element-plus";
import { SaveCallBackPatient } from "@/api/InspectionManagement/PatMenu";
const emits = defineEmits(["cancel", "confirm", "close"]);
const state = reactive({
ParentData: {
currentRow: {},
currentUserInfo: {},
formData: {},
tableData: {}
} as any,
visible: false
});
const dialogClose = () => {
emits("close");
};
const cancelBtn = () => {
emits("cancel");
state.visible = false;
};
const confirmBtn = () => {
// 召回原因不能为空
if (!state.ParentData.formData.obrValueD) {
ElMessage.info("请输入召回原因");
return;
}
const { currentRow, currentUserInfo, formData } = state.ParentData;
const params: any = {
callReason: formData.obrValueD,
repId: currentRow.repId,
sendUserId: currentUserInfo.userLoginid,
sendUserName: currentUserInfo.userName,
typeName: currentRow.patCtypeName ? currentRow.patCtypeName : "检验科"
};
SaveCallBackPatient(params).then((res: { success: any; msg: any }) => {
const { success, msg } = res;
if (success) {
ElMessage.success("召回成功");
cancelBtn();
} else {
ElMessage({
type: "error",
dangerouslyUseHTMLString: true,
message: msg
});
}
});
};
const Initialization = (initParam: { currentRow: any; currentUserInfo: any; formData: any; tableData: any }) => {
state.ParentData = initParam;
state.visible = true;
};
defineExpose({
Initialization
});
</script>
<style scoped></style>