动态组件扩展的核心价值:告别硬编码组件
传统开发中,每增加一个功能模块都需要:
- 硬编码组件引入:手动 import 并注册组件
- 维护成本激增:组件越多,代码越臃肿,修改牵一发动全身
而动态组件扩展设计通过 "配置驱动的组件管理" 彻底改变这一现状:
- 配置即组件:通过 DSL 配置自动生成组件实例
- 统一交互协议:所有组件遵循相同的生命周期和事件规范
- 按需加载:只渲染配置中声明的组件,避免冗余
elpis 动态组件架构解析
第一层:组件注册中心(ComponentConfig)
component-config.js
作为组件注册中心,采用 "键值映射" 模式管理所有动态组件:
css
// app/pages/dashboard/complex-view/schema-view/components/component-config.js
export const ComponentConfig = {
createForm: {
component: createForm,
},
editForm: {
component: editForm,
},
detailPanel: {
component: detailPanel,
}
}
设计亮点
- 解耦组件与业务:组件实现与业务逻辑分离,通过 key 建立关联
- 扩展性极强:新增组件只需在 ComponentConfig 中注册,无需修改主逻辑
第二层:Schema 转换引擎(buildDtoSchema)
动态组件的核心难题是 "如何将通用 schema 转换为特定组件的配置" 。elpis 通过 buildDtoSchema
函数实现智能转换:
javascript
// 核心转换逻辑
const buildDtoSchema = (_schema, comName) => {
if (!_schema.properties) return {}
const dtoSchema = {
type: 'object',
properties: {}
}
for (const key in _schema.properties) {
const props = _schema.properties[key];
// 关键:根据组件名提取对应配置
if (props[`${comName}Option`]) {
let dtoProps = {}
// 提取非 Option 的通用属性
for (const pKey in props) {
if (pKey.indexOf('Option') < 0) {
dtoProps[pKey] = props[pKey]
}
}
// 合并组件特定配置
dtoProps = Object.assign({}, dtoProps, {
option: props[`${comName}Option`]
})
dtoSchema.properties[key] = dtoProps
}
}
return dtoSchema
}
第三层:动态渲染与事件协调
在 schema-view.vue
中,通过 Vue 的动态组件机制实现组件的自动渲染和事件协调:
xml
<template>
<!-- 搜索面板 -->
<search-panel @search="onSearch" />
<!-- 数据表格 -->
<table-panel @operate="onTableOperate" />
<!-- 动态组件:核心实现 -->
<component
:is="ComponentConfig[key]?.component"
v-for="(component, key) in components"
:key="key"
ref="comListRef"
:component="component"
@command="onComponentCommand"
/>
</template>
<script setup>
// 事件映射机制
const EventHandlerMap = {
showComponent: showComponent,
}
// 根据 DSL 配置映射表格操作到组件显示
const onTableOperate = ({btnConfig, rowData}) => {
const { eventKey } = btnConfig;
if(EventHandlerMap[eventKey]) {
EventHandlerMap[eventKey]({btnConfig, rowData})
}
}
// 动态显示组件
function showComponent({btnConfig, rowData}) {
const { comName } = btnConfig.eventOption;
const component = comListRef.value.find(item => item.name === comName);
if(component) {
component.handleShow(rowData)
}
}
</script>
设计精髓
- 数据传递标准化 :通过
component
prop 传递 schema 和 config - 事件冒泡机制:组件内部事件可向上传递,实现跨组件通信
具体组件实现:以 CreateForm 为例
动态组件需要遵循统一的接口规范,以 create-form.vue
为例:
xml
<template>
<el-drawer v-model="isShow" @close="handleHide">
<template #header>
<h3>{{ title }}</h3>
</template>
<!-- 核心:使用转换后的 schema 渲染表单 -->
<schemaForm
ref="schemaFormRef"
:schema="components[name].schema"
/>
<template #footer>
<el-button @click="handleSave">{{ saveBtnText }}</el-button>
</template>
</el-drawer>
</template>
<script setup>
// 接收 DSL 数据
const { api, components } = inject('schemaViewData')
const name = ref('createForm')
// 统一接口实现
const handleShow = () => {
const { config } = components.value[name.value]
title.value = config.title
saveBtnText.value = config.saveBtnText
isShow.value = true
}
const handleSave = async () => {
const formData = schemaFormRef.value.getFormData()
await $curl({
url: api.value,
method: 'post',
data: formData
})
// 通知父组件刷新数据
emit('command', { event: 'loadTableData' })
}
// 暴露标准接口
defineExpose({
name,
handleShow,
handleHide
})
</script>
核心特性
- 配置驱动:组件的标题、按钮文案等都来自 DSL 配置
- Schema 复用:直接使用转换后的 schema 渲染表单,无需重复定义
- 事件通信:通过 emit 向上传递事件,实现数据联动
DSL 配置到组件的完整链路
让我们通过一个完整示例,展示从 DSL 配置到组件渲染的全流程:
1. DSL 配置定义
json
{
"schemaConfig": {
"api": "/api/user",
"schema": {
"type": "object",
"properties": {
"userName": {
"type": "string",
"label": "用户名",
"createFormOption": {
"comType": "input",
"required": true,
"placeholder": "请输入用户名"
}
}
}
},
"tableConfig": {
"headerButtons": [
{
"label": "新增用户",
"eventKey": "showComponent",
"eventOption": { "comName": "createForm" }
}
]
},
"componentConfig": {
"createForm": {
"title": "新增用户",
"saveBtnText": "确认新增"
}
}
}
}
2. Schema 转换处理
arduino
// useSchema Hook 中的处理
const { componentConfig } = sConfig;
const dtoComponents = {}
for(const key in componentConfig){
dtoComponents[key] = {
// 转换为组件专用 schema
schema: buildDtoSchema(configSchema, key),
// 组件配置
config: componentConfig[key]
}
}
components.value = dtoComponents
3. 动态组件渲染
xml
<!-- 自动渲染 createForm 组件 -->
<component
:is="ComponentConfig['createForm']?.component"
:component="{
schema: { /* 转换后的 createForm schema */ },
config: { title: '新增用户', saveBtnText: '确认新增' }
}"
/>
扩展新组件:从配置到实现
假设需要新增一个 "批量导入" 组件,整个流程如下:
1. 创建组件实现
xml
<!-- batch-import.vue -->
<template>
<el-dialog v-model="isShow">
<el-upload :action="uploadUrl">
<el-button>选择文件</el-button>
</el-upload>
</el-dialog>
</template>
<script setup>
const name = ref('batchImport')
const handleShow = () => {
isShow.value = true
}
defineExpose({ name, handleShow, handleHide })
</script>
2. 注册到 ComponentConfig
css
// component-config.js
import batchImport from './batch-import/batch-import.vue'
export const ComponentConfig = {
createForm: { component: createForm },
editForm: { component: editForm },
detailPanel: { component: detailPanel },
// 新增注册
batchImport: { component: batchImport }
}
3. 配置 DSL
css
{
"tableConfig": {
"headerButtons": [
{
"label": "批量导入",
"eventKey": "showComponent",
"eventOption": { "comName": "batchImport" }
}
]
},
"componentConfig": {
"batchImport": {
"title": "批量导入用户",
"uploadUrl": "/api/user/batch-import"
}
}
}
4. 自动生效
无需修改主逻辑代码,新组件即可自动参与渲染和事件处理。这就是动态组件扩展设计的威力所在。
展望:AI 时代的动态组件
随着 AI 技术发展,动态组件扩展设计将迎来新的可能:
- 智能组件生成:AI 根据业务描述自动生成组件代码和配置
- 自适应布局:根据用户行为动态调整组件排列和交互方式
- 个性化定制:为不同用户角色生成专属的组件组合
动态组件扩展设计不仅是技术架构的创新,更是开发思维的转变 ------ 从 "写代码实现功能" 到 "设计机制解决问题"。在 DSL 驱动的低代码时代,这种设计理念将成为构建可扩展、易维护系统的核心基石。
出处:《哲玄课堂-大前端全栈实践》