动态(按需)加载异步子组件
之前说过 ECharts 如何封装,今天来讲一讲 ECharts 如何做性能优化。
对于之前 ECharts 的封装子组件,我们可以使用 component 动态组件的方式进行渲染,并传参。
并且使用 import 动态导入搭配 defineAsyncComponent 实现打包代码分割,import 实现组件按需加载, defineAsyncComponent 实现将这些异步组件单独打包,减小主包的体积。
而且统一封装图表组件,统一管理图表配置 options,可以实现业务数据与图表样式配置分离。

以下代码均只保留核心代码,已脱敏。
index.vue 中去实现动态渲染:
<component
class="w-full"
:is="echartsComponent"
v-if="echartsComponent"
v-bind="echartsProps"
/>
// 接收参数
const props = defineProps({
confgData: {
type: Object,
required: true,
default: () => ({}),
},
boardData: {
type: Object,
required: true,
default: () => ({}),
},
// componentKey: {
// type: String,
// required: true,
// },
});
// ...
// 所有的图表按需加载
const allEcharts = [
{
type: 'bar',
component: defineAsyncComponent(() => import('./BarEcharts/index.vue')),
},
{
type: 'pie',
component: defineAsyncComponent(() => import('./PieCharts/index.vue')),
},
{
type: 'line',
component: defineAsyncComponent(() => import('./LineCharts/index.vue')),
},
{
type: 'progress',
component: defineAsyncComponent(() => import('./Progress/index.vue')),
},
];
// 抛出一个父组件使用该组件修改内部配置的方式
const emit = defineEmits(['updates']);
// 针对不同图表做不同的参数传递
const echartsProps = computed(() => {
if (confgData.value?.type === 'bar') {
return {
xAxisLabels: props.boardData.x_axis_labels,
yAxisLabels: props.boardData.y_axis_labels,
seriesData: props.boardData.series_data,
isHorizontal: props.boardData?.y_axis_labels?.length >= 1,
};
} else if (confgData.value?.type === 'pie') {
return {
data: props.boardData.data,
title: props.boardData?.title,
};
} else if (confgData.value?.type === 'progress') {
return {
percent: props.boardData.percent,
title: props.boardData?.title,
};
} else {
// 折线图
// obj 特殊化配置
return {
xAxisLabels: props.boardData.x_axis_labels,
yAxisLabels: props.boardData.y_axis_labels,
seriesData: props.boardData.series_data,
isHorizontal: props.boardData?.y_axis_labels?.length > 1,
...obj,
};
}
});
// 计算得到所需组件
const echartsComponent = computed(() => {
return allEcharts.find((item) => item.type === confgData.value?.type)?.component;
});
按需导入 ECharts 包
按需引入 ECharts,而非全量引入,减小打包体积大小。
在全局入口文件中引用。
lib/echarts.ts
import * as echarts from 'echarts/core';
import {
BarChart,
LineChart,
// ...
} from 'echarts/charts';
import {
TitleComponent,
TooltipComponent,
// ...
} from 'echarts/components';
import { SVGRenderer } from 'echarts/renderers';
echarts.use([
LegendComponent,
TitleComponent,
// ...
]);
export default echarts;
依赖预构建
依赖预构建 (Dependency Pre-Bundling)。
vite.config.ts
文件中,相关的配置是 optimizeDeps.include
:
typescript
// vite.config.ts
export default defineApplicationConfig({
overrides: {
optimizeDeps: {
include: [
'echarts/core',
'echarts/charts',
'echarts/components',
'echarts/renderers',
// ... other dependencies
],
},
// ...
},
});
这个配置是如何优化 ECharts 的?
-
优化对象 :此优化主要针对 开发环境 (
dev server
),旨在提升开发时的页面加载速度和热更新性能。 -
工作原理:
- 问题背景 :
- ECharts 库本身是由大量的小模块组成的(例如,
core
是核心,charts
目录下有各种图表类型,components
目录下有提示框、图例等组件)。 - 当我们在代码中按需引入时(如
import { BarChart } from 'echarts/charts'
),在开发模式下,浏览器需要根据import
语句逐个去请求这些零散的模块文件。 - 如果一个复杂的图表需要几十个模块,就会导致浏览器发起大量的网络请求,形成"请求瀑布流",严重拖慢页面首次加载速度。
- ECharts 库本身是由大量的小模块组成的(例如,
- Vite 的解决方案 :Vite 在启动开发服务器时,会先扫描项目代码,找出所有依赖。对于
optimizeDeps.include
中明确列出的依赖,Vite 会使用速度极快的esbuild
工具,提前将这些零散的模块 "预构建" 成一个或少数几个大的 JavaScript 文件。- 具体到 ECharts :配置中的
'echarts/core'
,'echarts/charts'
,'echarts/components'
,'echarts/renderers'
告诉 Vite:"请提前把这几个路径下的所有 ECharts 模块都找到,并将它们打包成一个整体。"
- 具体到 ECharts :配置中的
- 问题背景 :
-
带来的好处:
- 减少网络请求:经过预构建,当浏览器需要加载 ECharts 时,不再是请求几十个零散的小文件,而是只请求一个或几个已经打包好的大文件。这极大地减少了 HTTP 请求开销。
- 模块格式转换 :
esbuild
会将可能存在的 CommonJS 或 UMD 格式的模块(一些旧的库可能还在使用)统一转换为浏览器原生支持的 ESM (ES Modules) 格式,避免了在浏览器端进行复杂的模块解析。 - 更快的页面加载:最终结果就是,在开发环境中,包含 ECharts 图表的页面加载速度会得到显著提升,让开发体验更加流畅。
注意 :这个优化主要作用于 开发环境 。在 生产环境打包 (
build
) 时,Vite 会利用 Rollup 和 Tree Shaking 机制,确保最终打包出的代码只包含实际用到的 ECharts 模块,以实现生产环境包体积的最小化。这里的optimizeDeps
配置与最终的打包体积没有直接关系。
图表的屏幕适配
统一解决了窗口缩放时图表的适应问题,避免因窗口缩放导致的 ECharts 图表变形。(主要针对大屏)
- 使用 scale,缩放比例比较小,那么左右留白比较大。
- 推荐使用 vw/vh,动态计算宽高比例,字体等,比较灵活,避免大片留白。缺点:每个图表都需要单独做字体、间距、位移的适配,比较麻烦。