一、你的表單,是否正在失控?
想象一個場景,你正在開發一個“企業貸款申請”或“保險理賠”系統。
最初,頁面只有 5 個字段,你寫得優雅從容。隨着業務迭代,表單像吹氣球一樣膨脹到了 50 多個字段:“如果用户選了‘個體工商户’,不僅要隱藏‘企業法人’字段,還得去動態請求‘經營地’的下拉列表,同時‘註冊資本’的校驗規則還要從‘必填’變成‘選填’……”
於是,你的 Vue 文件變成了這樣:
<template>裏塞滿了深層嵌套的v-if和v-show。<script>裏到處是監聽聯動邏輯的watch和冗長的if-else。- 最痛苦的是: 當後端決定調整字段名,或者公司要求把這套邏輯複用到小程序時,你發現邏輯和 UI 已經像麻繩一樣死死纏在一起,拆不開了。
“難道寫表單,真的只能靠體力活嗎?”
為了擺脱這種低效率重複,我們嘗試將 中間件思想 引入 Vue 3,把複雜的業務規則從 UI 框架中剝離出來。今天,我就把這套“一次編寫,到處複用”的工程化方案分享給你。
二、 核心思想:讓數據自帶“説明書”
傳統模式下,前端像是一個**“搬運工”:拿到後端數據,手動判斷哪個該顯、哪個該隱。
而工程化模式下,前端更像是一個“組裝廠”**:數據在進入 UI 層之前,先經過一套“中間件流水線”,數據會被自動標註上 UI 描述信息(Schema)。
1. 什麼是 Schema 推斷?
數據不再是冷冰冰的鍵值對,而是變成了一個包含“元數據”的對象。通過 TypeScript 的類型推斷,我們讓數據自己告訴頁面:
- 我應該用什麼組件渲染(
componentType) - 我是否應該被顯示(
visible) - 我依賴哪些字段(
dependencies) - 我的下拉選項去哪裏拉取(
request)
2. UI 框架只是“皮膚”
既然邏輯都抽離到了框架無關的中間件裏,那麼 UI 層無論是用 Ant Design 還是 Element Plus,都只是換個“解析器”而已。
三、 實戰:構建 Vue 3 自動化渲染引擎
1. 組件註冊表
首先,我們要定義一個組件映射表,把抽象的字符串類型映射為具體的 Vue 組件。
TypeScript
// src-vue/components/FormRenderer/componentRegistry.ts
import NumberField from '../FieldRenderers/NumberField.vue'
import SelectField from '../FieldRenderers/SelectField.vue'
import TextField from '../FieldRenderers/TextField.vue'
import ModeToggle from '../FieldRenderers/ModeToggle.vue'
export const componentRegistry = {
number: NumberField,
select: SelectField,
text: TextField,
modeToggle: ModeToggle,
} as const
2. 組裝線:自動渲染器(AutoFormRenderer)
這是我們的核心引擎。它不關心業務,只負責按照加工好的 _fieldOrder 和 _schema 進行遍歷。
<template>
<a-row :gutter="[16,16]">
<template v-for="key in orderedKeys" :key="key">
<component
v-if="shouldRender(key)"
:is="resolveComponent(key)"
:value="data[key]"
:config="schema[key].fieldConfig"
:dependencies="collectDeps(schema[key])"
:request="schema[key].request"
@update:value="onFieldChange(key, $event)"
/>
</template>
</a-row>
</template>
<script setup lang="ts">
const props = defineProps<{ data: any }>();
const schema = computed(() => props.data?._schema || {});
const orderedKeys = computed(() => props.data?._fieldOrder || Object.keys(props.data));
// 根據中間件注入的 visible 函數判斷顯隱
function shouldRender(key: string) {
const s = schema.value[key];
if (!s || s.fieldConfig?.hidden) return false;
return s.visible ? s.visible(props.data) : true;
}
function resolveComponent(key: string) {
const type = schema.value[key]?.componentType || 'text';
return componentRegistry[type];
}
</script>
3. 原子化:會“思考”的字段組件
以 SelectField 為例,它不再是被動等待賦值,而是能感知依賴。當它依賴的字段(如“省份”)變化時,它會自動重新調用 request。
<script setup lang="ts">
const props = defineProps(['value', 'dependencies', 'request']);
const options = ref([]);
async function loadOptions() {
if (props.request) {
options.value = await props.request(props.dependencies || {});
}
}
// 深度監聽依賴變化,實現聯動效果
watch(() => props.dependencies, loadOptions, { deep: true, immediate: true });
</script>
四、 方案的“真香”時刻
1. 邏輯與 UI 的徹底解耦
所有的聯動規則、校驗邏輯、接口請求都定義在獨立於框架的 src/core 下。如果你明天想把項目從 Vue 3 遷到 React,你只需要重寫那幾個基礎字段組件,核心業務邏輯 一行都不用動。
2. “潔癖型”提交
很多動態表單方案會將 visible、options 等 UI 狀態混入業務數據,導致傳給後端的 JSON 極其混亂。我們的方案在提交前會運行一次“清洗中間件”:
const cleanPayload = submitCompileOutputs(formData.compileOutputs);
// 自動剔除所有以 _ 開頭的輔助字段和臨時狀態
後端拿到的永遠是乾淨、純粹的業務模型。
3. 開發體驗的飛躍
現在,當後端新增一個字段時,你的工作流變成了:
- 在類型推斷引擎里加一行規則。
-
刷新頁面,字段已經按預定的位置和樣式長好了。
你不再需要去 .vue 文件裏翻找幾百行處的 template 插入 HTML,更不需要擔心漏掉了哪個 v-if。
結語:不要為了用框架而用框架
很多時候,我們覺得 Vue 或 React 難維護,是因為我們將過重的業務決策交給了視圖層。
通過引入中間件和 Schema 推斷,我們實際上在 UI 框架之上建立了一個“業務邏輯防火牆”。Vue 只負責監聽交互和渲染結果,而變幻莫測的業務規則被關在了純 TypeScript 編寫的沙盒裏。
這種“工程化”的思維,不僅是為了今天能快速復刻功能,更是為了明天業務變動時,我們能優雅地“配置升級”,而不是“推倒重來”。
你是如何處理複雜表單聯動的?歡迎在評論區分享你的“避坑”指南!