vite+vue3 动态导入组件, 动态渲染组件
动态全局导入
1. 创建文件 plugins/componentLoader.js
(全局导入)
:is 的渲染条件是其绑定的值(如 component )必须是一个已注册的组件(可以是组件对象、异步组件、字符串组件名等)。这里全局注册了所以可以使用 componentName,也可使用组件进行渲染
注意!componentName名称,否者会无法渲染组件
import { defineAsyncComponent } from 'vue'
/**
* 自动注册所有图表组件,并按类型分类管理
* @param {VueApp} app - Vue应用实例
*/
export function loadComponents(app) {
// 定义不同类型图表组件的加载路径
// 使用Vite的import.meta.glob进行动态导入(懒加载模式)
const moduleTypes = {
bing: import.meta.glob('../components/echarts/bing/**/*.vue', { eager: false }),
bar: import.meta.glob('../components/echarts/bar/**/*.vue', { eager: false }),
line: import.meta.glob('../components/echarts/line/**/*.vue', { eager: false }),
map: import.meta.glob('../components/echarts/map/**/*.vue', { eager: false }),
}
// 存储组件元数据的上下文对象
const context = {}
// 遍历每种图表类型
Object.entries(moduleTypes).forEach(([moduleType, modules]) => {
// 初始化该类型下的组件元数据数组
context[moduleType] = []
// 遍历该类型下的所有组件
Object.entries(modules).forEach(([path, module]) => {
// 从路径中提取组件名称(例如从"../components/echarts/bar/bar01/Chart.vue"提取"bar01")
const pathArgs = path.split('/')
const componentName = pathArgs[pathArgs.length - 2]
// 保存组件元数据到上下文, 注意!componentName为组件的名称,且 key 必须为 componentName
context[moduleType].push({
componentName, // 组件名称
path, // 组件路径
type: moduleType, // 组件类型
})
// 注册组件为异步组件(按需加载)
app.component(componentName, defineAsyncComponent(module))
})
})
// 将组件元数据添加到Vue应用配置中,便于后续使用
app.config.DynamicComponents = context
// app.config.globalProperties.DynamicComponents = context
}
2. 导入组件 main.js
import './assets/main.css'
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
// 自动加载所有图表组件
import { loadComponents } from './plugins/componentLoader'
const app = createApp(App)
loadComponents(app) // 自动加载所有图表组件
app.use(router)
app.mount('#app')
3. 使用,动态渲染导入的分类组件
<template>
<!-- 循环渲染所有动态注册的组件 -->
<div
style="border: 2px solid salmon"
v-for="component in components"
:key="component.componentName"
>
<!-- 动态组件渲染,:is绑定组件名称 -->
<!-- 双击时打印组件元数据到控制台 -->
<component :is="component.componentName" @dblclick="console.log(component)" />
</div>
</template>
<script setup>
import { shallowReactive } from 'vue'
import { getCurrentInstance } from 'vue'
// 获取当前组件实例
const internalInstance = getCurrentInstance()
// 获取应用上下文
const ctx = internalInstance.appContext
// 从应用配置中获取动态组件元数据(在loadComponents函数中注册)
const dynamicComponents = ctx.config.DynamicComponents
// const dynamicComponents = ctx.config.globalProperties.DynamicComponents
// 使用浅响应式存储组件列表(仅监听对象引用变化)
const components = shallowReactive([])
// 获取所有组件类型
const keys = Object.keys(dynamicComponents)
// 扁平化处理组件元数据
// 将不同类型的组件(饼图/柱状图等)合并到一个数组中
for (const key of keys) {
for (const item of dynamicComponents[key]) {
components.push({
componentName: item.componentName, // 组件名称
type: item.type, // 组件类型(如bing/bar/line/map)
path: item.path, // 组件文件路径
})
}
}
</script>
<style scoped></style>
动态局部导入
:is 的渲染条件是其绑定的值(如 component )必须是一个已注册的组件(可以是组件对象、异步组件、字符串组件名等)。因为组件没有注册所以:is不能是组件名称,要有render和setup方法的组件
同步加载
同步加载(eager: true)会在应用启动时一次性解析并加载所有组件。同步加载适合组件较少或需要立即使用的场景。
<template>
<div class="echarts-item" v-for="(component, index) in componentsList" :key="index">
<!-- 动态组件渲染,:is绑定组件对象 -->
<component :is="component.component" />
</div>
</template>
<script setup>
/**
* 动态导入组件模块
* 使用Vite的glob导入功能批量加载指定目录下的所有Usage.vue文件
* 路径模式:../components/echarts/bar/** /Usage.vue
* eager: true 表示立即导入,而非懒加载
*/
const modules = import.meta.glob('../components/echarts/bar/**/Usage.vue', { eager: true })
/**
* 生成组件列表数据结构
* 将导入的模块转换为可用的组件配置列表
* 每个配置项包含:
* - name: 组件名称(从路径提取)
* - component: 组件对象
*/
const componentsList = Object.entries(modules).map(([path, module]) => {
// 从路径中提取组件名称
// 例如:路径为"../components/echarts/bar/bar01/Usage.vue"
// 提取结果为"bar01"
const componentName = path.split('/').slice(-2, -1)[0]
// 返回组件配置对象
return { name: componentName, component: module.default }
})
</script>
异步加载
异步加载(defineAsyncComponent + eager: false)只会在用户实际访问路由时才加载对应组件,初始加载时只需加载路由配置和必要的基础代码,大大减少了首屏加载时间。
<template>
<div class="wrapper">
<div class="echarts-list">
<div class="echarts-item" v-for="(component, index) in componentsList" :key="index">
<!-- 动态渲染组件 -->
<component :is="component.component" @click="dblclick(component)" />
</div>
</div>
</div>
</template>
<script setup>
import { defineAsyncComponent } from 'vue'
const modules = import.meta.glob('../components/echarts/bar/**/Usage.vue', { eager: false })
const componentsList = Object.entries(modules).map(([path, loadComponent]) => {
// 提取组件名称
const componentName = path.split('/').slice(-2, -1)[0]
// 使用 defineAsyncComponent 创建异步组件
const asyncComponent = defineAsyncComponent(loadComponent)
return { name: componentName, component: asyncComponent }
})
const dblclick = (component) => {
alert(JSON.stringify(component))
}
</script>