32 个 Vue 组件的设计取舍

发布时间:2026/5/25 6:57:10

32 个 Vue 组件的设计取舍 32 个 Vue 组件的设计取舍文章目录32 个 Vue 组件的设计取舍决策一不用 adapter 层决策二字典通过 inject 中心化决策三provide/inject 做绑定不用 v-model32 个组件的其他决策一些经验总结做 low-code 平台难的不是写组件是做选择。browise-vue 有 32 个组件分布在 7 个分类里form/ 18 个TextBox、NumberBox、ComboBox、DateBox…… grid/ 1 个MetaGrid tree/ 2 个MetaTree、ComboBoxTree container/ 4 个FieldSet、FlexContainer、TabContainer、Panel feedback/ 4 个Button、Dialog、MessageBox、ContextMenu business/ 3 个UserSelect、DepartmentSelect、LinkTable editor/ 2 个Editor、MarkdownEditor组件本身不复杂——多数是 Element Plus 的一层薄封装。复杂的不是实现是决定怎么封装。这篇文章聊三个关键的设计决策。决策一不用 adapter 层做 low-code 组件库常见的设计是写一层 adapter也叫桥接interfaceIUiAdapter{renderInput(props):VNoderenderSelect(props):VNode// ... 几十个方法}classElementPlusAdapterimplementsIUiAdapter{/* ... */}classAntDesignAdapterimplementsIUiAdapter{/* ... */}当初 VedioCall 就用了这模式——BaseComp.java 128 个 adapter 子类每个 UI 库一套。这次我决定不用 adapter直接依赖 Element Plus。原因很简单没有切换 UI 库的需求——你不会今天用 Element Plus明天换 Ant Designadapter 的抽象成本太高——每个 UI 库的 API 差异大到 adapter 要么是最小公分母要么全是 if/elseadapter 层拦截了 TypeScript 类型——你在 adapter 里写any组件层的类型推断就断了对比一下两种写法有 adapteradapter.renderSelect({value:row[field],options:dict[field],onChange:(v){row[field]v}})无 adapterel-select :model-valuevalue update:model-valueonChange el-option v-foritem in options ... / /el-select后者类型推断直接来自 Element Plus 的官方定义编辑器体验好得多。这个决策的代价是如果以后要切 UI 库32 个组件全部要改。但我认为这个代价不会发生。决策二字典通过 inject 中心化表单组件最麻烦的事情不是渲染是数据来源。一个 ComboBox 的下拉选项哪里来方案 A父组件传 propsb-combo-box :optionsgenderOptions /看起来直接但跨组件共享选项时grid 也要显示中文、表单也要选你得写两份constgenderOptions[...]constgridFormatters{gender:vmap.get(v)}方案 B组件自感知 FieldMetaFieldMeta 里带 optionsfields:[{field:gender,options:[{value:1,label:男}]}]组件通过 inject 拿到 store查 field 同名 metadataconstmetainject(store).meta.fields.find(ff.fieldprops.field)constoptionsmeta?.options但我最终选了方案 C独立字典中心constdictuseDictProvider()dict.registerAll({GENDER:[{value:1,label:男},{value:2,label:女}]})// 后端对接时dict.setLoader(async(codesName){returnhttpClient.request({url:/ea/codes/${codesName}})})组件不需要知道 options 从哪里来也不需要传 propsb-combo-box fieldgender /它只做一件事读 FieldMeta 的codes: GENDER然后从 DictCenter 里查。好处Grid 和 ComboBox 共享字典同一个 codes 名输出一致后端切换本地注册 → 远程加载不需要改任何组件和 Dojo 时代的decoder{store:AAC004}一个思路决策三provide/inject 做绑定不用 v-model表单组件最核心的交互读数据、写数据、标记脏数据。最常规的做法是 v-modelb-text-box v-modelform.name label姓名 /问题v-model 需要父组件维护 form 对象并且没有脏标记能力。browise 采用的是 provide/inject 隐式绑定!-- 不需要 v-model不需要传 form -- b-text-box fieldname label姓名 / b-combo-box fieldgender label性别 / b-date-box fieldhireDate label入职日期 /底层实现// 父组件在 setup 阶段提供绑定上下文constcurrentRowprovideBinding(store)// 点击表格行切换编辑目标currentRow.valueclickedRow// 所有组件自动切换每个组件内部通过 inject 拿到当前行通过 computed 双向绑定constctxinject(BINDING_KEY)!constvaluecomputed({get:()ctx.currentRow.value?.getItemValue(props.field)??,set:(v)ctx.currentRow.value?.setItemValue(props.field,v)})好处不需要每一层传递 form 对象表格行切换时所有表单组件自动更新脏标记自动追踪setItemValue内部处理了坏处一个页面上只有一套绑定上下文一个 form editor需要 MultiBinding 模式来处理多个编辑区域32 个组件的其他决策组件关键决策原因MetaGrid显示用toRaw()事件用$rowIndex反查 Rowvxe-table 不认嵌套对象ComboBoxonMounted时dict.ensure(codes)懒加载减少首屏请求Range两个 field 字段startField / endField日期范围和数字范围复用同一组件Switch存1/0字符串兼容数据库 CHAR(1) 类型FileUpload逗号分隔文件列表单字段存多文件不建关联表EditorwangEditor 5轻量级富文本不需要 Quill/TinyMCEPopupdisplayField 显示文本field 存值单选弹出选择器通用模式ContextMenuTeleport 挂载到 body避免 overflow: hidden 截断FlexContainerCSS flexbox不依赖任何 UI 库布局组件应该最轻量FieldSet原生 HTML fieldset legend不需要 Element Plus 的组件包装一些经验Q什么时候该封装一个组件我的标准当业务逻辑和渲染逻辑不能干净分开时封装。比如 ComboBox——从字典取选项、存值到 Row、标记脏数据是业务逻辑渲染输入框是渲染逻辑。这两者一旦耦合在业务代码里就会在每个页面里重复。Q什么时候不该封装按钮不需要封装——el-button本身已经够用了。但我还是封装了为了统一控制样式和权限。这是妥协。Q23 种字段类型够用吗够。从政务系统的实际经验看Text、Number、Combo、Date 四类占了 80% 的字段。其余 19 种覆盖了剩下的需求。省市区级联没有加——它太依赖具体业务的数据结构。总结组件设计的三条原则不要为了可能的未来做抽象——adapter 层就是为永远不发生的 UI 切换付出的成本数据来源应该是组件的隐式依赖而非显式参数——字典中心比 props 更适合跨组件共享数据绑定协议比 v-model 更适合低代码场景——父组件不需要管理 form 对象切换编辑目标只需要一次赋值

相关新闻