配置驱动的边界问题
Elpis 通过配置驱动解决了 80% 的中后台 CRUD 场景,但总会遇到内置组件无法覆盖的情况:
- 需要省市区三级联动选择器
- 需要带千分位格式化的金额输入框
- 需要集成公司自研的图片裁剪上传组件
- 需要富文本编辑器、图表组件等第三方库
这时候有三个选择:
方案 A:放弃配置驱动,回到手写代码
方案 B:等框架作者更新内置组件
方案 C:自己扩展组件,像内置组件一样使用
Elpis 选择了方案 C,通过动态组件扩展机制,让框架既保持标准化,又具备灵活性。
核心设计:一个"字符串"的魔法
Elpis 的扩展机制说穿了就一个核心思想:配置里写的是字符串,渲染时才决定用哪个组件。
看这段配置:
product_name: {
createFormOption: {
comType: 'input', // 这只是个字符串
}
}
这个 'input' 不是直接对应某个组件,而是一个"代号"。真正的组件在哪?在一个叫"注册中心"的地方:
// form-item-config.js
const FormItemConfig = {
'input': { component: InputComponent },
'select': { component: SelectComponent },
'richEditor': { component: RichEditorComponent }
};
渲染时,Elpis 做的事情很简单:
<component :is="FormItemConfig[配置里的comType].component" />
就这样,配置和组件解耦了。你想加新组件?往注册中心加一行,配置里就能用。
这个设计妙在哪?
1. 配置稳定:
即使你把 InputComponent 整个重写了,配置文件一个字都不用改。因为配置里只是写了个 'input' 字符串。
2. 场景隔离:
搜索栏有自己的注册中心,表单有自己的注册中心。同样是 'input',在搜索栏可能是个简单输入框,在表单里可能是个带校验的复杂组件。
3. 扩展简单: 不需要改框架代码,不需要发 PR,不需要等更新。自己加一行注册,立刻就能用。
实战:扩展一个富文本编辑器组件
通过实际案例演示如何扩展组件。假设需要添加富文本编辑器支持。
第一步:实现组件
创建文件 app/pages/widgets/schema-form/complex-view/rich-editor/rich-editor.vue:
<template>
<div class="form-item">
<div class="item-label">
<span>{{ schema.label }}</span>
<span v-if="schema.option?.required" class="required">*</span>
</div>
<div class="item-value">
<QuillEditor v-model:content="value" />
<div v-if="!isValid" class="valid-tips">{{ validMessage }}</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { QuillEditor } from '@vueup/vue-quill';
const props = defineProps({
schemaKey: String, // 字段名
schema: Object, // 字段配置
model: String // 初始值
});
const value = ref(props.model || '');
const isValid = ref(true);
const validMessage = ref('');
// 必须实现的接口方法
const validate = () => {
if (props.schema.option?.required && !value.value) {
isValid.value = false;
validMessage.value = '这个字段必填';
return false;
}
isValid.value = true;
return true;
};
const getValue = () => {
return { [props.schemaKey]: value.value };
};
defineExpose({ validate, getValue });
</script>
关键约定:
- Props 必须包含
schemaKey、schema、model - 必须暴露
validate()和getValue()方法 - 其他实现细节可自由发挥
第二步:注册组件
在 app/pages/widgets/schema-form/form-item-config.js 中注册:
import richEditor from "./complex-view/rich-editor/rich-editor.vue";
const FormItemConfig = {
input: { component: input },
select: { component: select },
richEditor: { component: richEditor } // 新增注册
};
第三步:配置使用
在业务模型中使用新组件:
product_description: {
type: 'string',
label: '商品描述',
createFormOption: {
comType: 'richEditor', // 使用扩展组件
required: true
}
}
完成。刷新页面,富文本编辑器自动渲染,校验、提交等功能自动生效。
背后的技术:Vue 3 的动态组件
你可能好奇 Elpis 是怎么做到"运行时决定渲染哪个组件"的。答案是 Vue 3 的 <component :is>。
看 Elpis 的核心渲染代码:
<template>
<template v-for="(itemSchema, key) in schema.properties">
<component
:is="FormItemConfig[itemSchema.option?.comType]?.component"
:schemaKey="key"
:schema="itemSchema"
:model="model[key]"
/>
</template>
</template>
这段代码在做什么?
- 遍历配置里的每个字段
- 读取字段的
comType(比如'input') - 从注册中心找到对应的组件(
FormItemConfig['input'].component) - 用
:is动态渲染这个组件
关键点::is 后面可以是一个变量,这个变量的值是什么组件,就渲染什么组件。
这就是为什么你改配置文件就能换组件——因为组件是运行时决定的,不是编译时写死的。
一个容易忽略的细节:统一接口
注意到没有,所有组件都接收同样的 props:
:schemaKey="key"
:schema="itemSchema"
:model="model[key]"
这是 Elpis 的"约定"。只要你的组件遵守这个约定,就能被动态渲染。
这就像 USB 接口,不管你是键盘、鼠标还是 U 盘,只要接口对得上,就能插上用。
所以写扩展组件时,记住三件事:
- Props 要有
schemaKey、schema、model - 要暴露
validate()和getValue()方法 - 其他的随便你发挥
对比:Elpis vs 其他方案
vs Element Plus / Ant Design(组件库)
组件库:给你一堆组件,你自己拼。
<el-form>
<el-form-item label="商品名称">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="价格">
<el-input-number v-model="form.price" />
</el-form-item>
<!-- 每个字段都要写 -->
</el-form>
Elpis:写个配置,自动生成。
{
product_name: { createFormOption: { comType: 'input' } },
price: { createFormOption: { comType: 'inputNumber' } }
}
结论:组件库灵活但重复劳动多,Elpis 标准化但省事。适用场景不同,不是替代关系。
vs Formily / React JSON Schema Form(表单方案)
JSON Schema 表单:只管表单,其他的你自己搞。
Elpis:搜索 + 表格 + 表单 + 详情,一套配置全搞定。
结论:Elpis 是 JSON Schema 思想在整个中后台系统的延伸。
写在最后
Elpis 的动态组件扩展机制核心就三件事:
- 配置里写字符串标识,不直接引用组件
- 用注册中心做类型映射,字符串对应具体组件
- 用 Vue 的
:is实现运行时动态渲染
这套设计让框架在标准化和灵活性之间找到了平衡:
- 80% 的场景用内置组件,配置驱动,快速开发
- 20% 的场景扩展组件,一次封装,到处复用
扩展组件的成本是一次性的,但收益是长期的。当你的组件库逐渐丰富,配置驱动的威力就会越来越明显。
框架的价值不在于限制开发者,而在于提供清晰的扩展路径,让开发者在需要时能够突破标准化的边界。
引用: 抖音“哲玄前端”《大前端全栈实践》