普通视图

发现新文章,点击刷新页面。
昨天 — 2025年12月21日首页

从硬编码到 Schema 推断:前端表单开发的工程化转型

作者 光头老石
2025年12月21日 20:01

一、你的表单,是否正在失控?

想象一个场景,你正在开发一个“企业贷款申请”或“保险理赔”系统。

最初,页面只有 5 个字段,你写得优雅从容。随着业务迭代,表单像吹气球一样膨胀到了 50 多个字段: “如果用户选了‘个体工商户’,不仅要隐藏‘企业法人’字段,还得去动态请求‘经营地’的下拉列表,同时‘注册资本’的校验规则还要从‘必填’变成‘选填’……”

于是,你的 Vue 文件变成了这样:

  • <template> 里塞满了深层嵌套的 v-ifv-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. “洁癖型”提交

很多动态表单方案会将 visibleoptions 等 UI 状态混入业务数据,导致传给后端的 JSON 极其混乱。我们的方案在提交前会运行一次“清洗中间件”:

const cleanPayload = submitCompileOutputs(formData.compileOutputs);
// 自动剔除所有以 _ 开头的辅助字段和临时状态

后端拿到的永远是干净、纯粹的业务模型。

3. 开发体验的飞跃

现在,当后端新增一个字段时,你的工作流变成了:

  1. 在类型推断引擎里加一行规则。

  2. 刷新页面,字段已经按预定的位置和样式长好了。

    你不再需要去 .vue 文件里翻找几百行处的 template 插入 HTML,更不需要担心漏掉了哪个 v-if。


结语:不要为了用框架而用框架

很多时候,我们觉得 Vue 或 React 难维护,是因为我们将过重的业务决策交给了视图层

通过引入中间件和 Schema 推断,我们实际上在 UI 框架之上建立了一个“业务逻辑防火墙”。Vue 只负责监听交互和渲染结果,而变幻莫测的业务规则被关在了纯 TypeScript 编写的沙盒里。

这种“工程化”的思维,不仅是为了今天能快速复刻功能,更是为了明天业务变动时,我们能优雅地“配置升级”,而不是“推倒重来”。


你是如何处理复杂表单联动的?欢迎在评论区分享你的“避坑”指南!

❌
❌