- 前端
- UI组件
【免费下载链接】formily
📱🚀 🧩 Cross Device & High Performance Normal Form/Dynamic(JSON Schema) Form/Form Builder -- Support React/React Native/Vue 2/Vue 3
导读
本文聚焦 @formily/vue 协议驱动体系中SchemaField组件的JSON Schema 模式——即不借助 JSX/模板标记,而是直接向SchemaField传入一份遵循 JSON-Schema 规范的普通对象来渲染表单。你将掌握createSchemaField工厂函数的用法、x-component等协议字段的语义、表达式作用域与联动协议的书写方式,以及 Schema 对象从 JSON 到字段模型的转换原理,从而在 Vue 2/Vue 3 项目中实现"一份 JSON 驱动整张表单"的纯配置化开发。
SchemaField 的两种使用模式
在 @formily/vue 中,SchemaField组件是专门用于解析 JSON-Schema 动态渲染表单的组件,它存在两种等价的使用形态:
- Markup Schema 模式:在
SchemaField内部嵌套SchemaStringField、SchemaObjectField等标记组件,以"模板写协议"的方式声明 Schema(详见 schema-field.md)。 - JSON Schema 模式:不写任何子节点,直接将一份 JSON-Schema 对象通过
schema属性传入组件。这是本文的主题,也是最契合"后端下发配置 / 动态渲染"场景的用法。
两种模式最终都会收敛到同一棵 Schema 树,由SchemaField内部统一交给RecursionField递归渲染,因此它们表达的协议能力完全一致,区别只在于 Schema 的来源(模板标记 vs 纯 JSON 数据)。
快速上手:直接传入 Schema 对象
官方文档给出的用例(schema-field-with-schema.vue)是最精简的入门示例:
<template> <FormProvider :form="form"> <SchemaField :schema="{ type: 'object', properties: { input: { type: 'string', 'x-component': 'Input', }, }, }" > </SchemaField> </FormProvider> </template> <script> import { Input } from 'ant-design-vue' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/vue' import 'ant-design-vue/dist/antd.css' const { SchemaField } = createSchemaField({ components: { Input, }, }) export default { components: { FormProvider, SchemaField }, data() { return { form: createForm(), } }, } </script>这个示例完整展示了 JSON Schema 模式的三要素:
createForm():来自 @formily/core,创建一个表单核心实例,通过FormProvider注入组件树;createSchemaField({ components }):工厂函数,注册可供 Schema 协议引用的组件集合;:schema="...json...":一份纯 JSON 数据,其中'x-component': 'Input'声明字段使用注册表中的Input组件。
运行后页面上会出现一个受 Formily 表单模型管理的输入框,其值、校验、联动等全部由 JSON 协议驱动,模板中无需再写任何表单状态管理代码。
createSchemaField 工厂函数与组件注册
所有SchemaField变体都必须经由createSchemaField工厂函数创建(源码)。其签名定义如下(摘自 schema-field.md):
type ComposeSchemaField = { SchemaField: Vue.Component<any, any, any, ISchemaFieldProps> SchemaMarkupField: Vue.Component<any, any, any, ISchema> SchemaStringField: Vue.Component<any, any, any, Omit<ISchema, 'type'>> SchemaObjectField: Vue.Component<any, any, any, Omit<ISchema, 'type'>> SchemaArrayField: Vue.Component<any, any, any, Omit<ISchema, 'type'>> SchemaBooleanField: Vue.Component<any, any, any, Omit<ISchema, 'type'>> SchemaDateField: Vue.Component<any, any, any, Omit<ISchema, 'type'>> SchemaDateTimeField: Vue.Component<any, any, any, Omit<ISchema, 'type'>> SchemaVoidField: Vue.Component<any, any, any, Omit<ISchema, 'type'>> SchemaNumberField: Vue.Component<any, any, any, Omit<ISchema, 'type'>> } // 工厂函数参数属性 interface ISchemaFieldFactoryProps { components?: { [key: string]: Vue.Component // 组件列表 } scope?: any // 全局作用域,用于实现协议表达式变量注入 } // SchemaField 属性 interface ISchemaFieldProps extends IFieldFactoryProps { schema?: ISchema // 字段 schema scope?: any // 协议表达式作用域 name?: string // 字段名称 } // 工厂函数 interface createSchemaField { (props: ISchemaFieldFactoryProps): ComposeSchemaField }需要注意的关键点:
components是协议与组件之间的"翻译表":'x-component': 'Input'中的字符串'Input'会去components集合中按 Key 查找对应组件;x-decorator同理,两者都必须与createSchemaField传入的组件集合的 Key 精确匹配(见 schema.md 详细说明)。scope用于注入表达式变量:工厂函数级scope与组件级scope会被合并(lazyMerge),供协议中的{{expression}}表达式消费。在源码中,这个合并结果通过SchemaExpressionScopeSymbol向下 provide(SchemaField.ts)。- 除
SchemaField外,工厂还会返回 9 个标记组件,其中SchemaMarkupField是通用标记,其余为按类型(string/object/array/boolean/date/datetime/void/number)拆分的便捷标记,它们仅服务于 Markup 模式。
在SchemaField的 props 中,schema若是Schema实例则直接复用,否则会被包装为new Schema({ type: 'object', ...props.schema })——这意味着不传type时默认按object处理,且 JSON 会被立即转换成带方法的 Schema 树。
Schema 协议:一份 JSON 能描述什么
JSON Schema 模式的核心价值在于,Schema 对象的每个字段都会映射到 Formily 的字段模型上。下表整理了完整映射关系(摘自 schema.md),是书写协议时的"字典":
| 属性 | 描述 | 类型 | 字段模型映射 |
|---|---|---|---|
| type | 类型 | string \| object \| array \| number \| boolean \| void \| date \| datetime | GeneralField |
| title | 标题 | string | title |
| description | 描述 | string | description |
| default | 默认值 | any | initialValue |
| readOnly | 是否只读 | boolean | readOnly |
| writeOnly | 是否只写 | boolean | editable |
| enum | 枚举 | SchemaEnum | dataSource |
| const / multipleOf / maximum / minimum / maxLength / minLength / pattern / maxItems / minItems / uniqueItems / maxProperties / minProperties / required / format | 校验规则 | number \| string \| boolean | validator |
| properties | 对象属性描述 | Record<string, ISchema> | - |
| items | 数组项描述 | ISchema \| ISchema[] | - |
| additionalItems / patternProperties / additionalProperties | 扩展描述 | Schema | - |
| x-index | UI 展示顺序 | number | - |
| x-pattern | UI 交互模式 | FieldPatternTypes | pattern |
| x-display | UI 展示 | FieldDisplayTypes | display |
| x-validator | 字段校验器 | FieldValidator | validator |
| x-decorator | 字段 UI 包装器组件 | string \| 组件 | decorator |
| x-decorator-props | 包装器组件属性 | any | decorator |
| x-component | 字段 UI 组件 | string \| 组件 | component |
| x-component-props | UI 组件属性 | any | component |
| x-reactions | 字段联动协议 | SchemaReactions | reactions |
| x-content | 字段内容(子节点) | any | ReactChildren |
| x-visible / x-hidden / x-disabled / x-editable / x-read-only / x-read-pretty | 展示与交互状态 | boolean | visible / hidden / disabled / editable / readOnly / readPretty |
| definitions | Schema 预定义 | Record<string, ISchema> | - |
| $ref | 引用预定义并合并 | string | - |
| x-data | 扩展属性 | object | data |
在 JSON Schema 模式下,这份表格中除title、default、required、enum等标准 JSON-Schema 关键字可直接使用外,所有x-开头的 Formily 扩展字段也都原样可用。因此"直接传 JSON"并不意味着能力缩水——动态渲染一个带校验、带枚举、带装饰器的完整字段,可以写成:
{ "type": "object", "properties": { "name": { "type": "string", "title": "姓名", "required": true, "x-decorator": "FormItem", "x-decorator-props": { "labelCol": 6, "wrapperCol": 10 }, "x-component": "Input", "x-component-props": { "placeholder": "请输入姓名" }, "x-validator": { "minLength": 2 } } } }表达式:让 JSON 活起来的{{ }}语法
Schema 的每个属性都可以使用字符串表达式,约定为以{{开头、}}结尾的字符串即视为表达式片段。表达式变量可以从createSchemaField的scope传入,也可以从SchemaField组件的scope传入(schema.md 详细说明)。
例如,让默认值来自外部作用域变量:
<SchemaField :scope="{ userName: '张三' }" :schema="{ type: 'object', properties: { name: { type: 'string', 'x-component': 'Input', default: '{{userName}}' }, }, }" />在 @formily/json-schema 的编译实现中,Schema实例的compile(scope)方法会深度递归整棵 Schema 树,找出所有表达式片段并消费作用域变量(Schema.compile静态方法、shallowCompile浅层编译变体以及Schema.silent静默编译开关均有对应实现,见 schema.md 方法章节)。
内置表达式作用域
表达式中可直接消费以下内置变量(详见 schema.md 内置表达式作用域):
$self:当前字段实例,普通属性表达式与x-reactions中均可用;$values:顶层表单数据;$form:当前 Form 实例;$observable:创建响应式对象(用法同observable);$memo:创建持久引用数据(用法同autorun.memo);$effect:响应 autorun 首次执行的下一个微任务时机及 dispose(用法同autorun.effect);$dependencies/$deps:只能在x-reactions表达式中消费,与dependencies声明按数组顺序对应;$target:只能在x-reactions表达式中消费,代表主动联动模式下的 target 字段。
x-reactions:JSON 中的联动协议
x-reactions是 Schema 中最核心的联动协议,同样支持"主动联动"与"被动联动"两种模式(完整定义见 schema.md SchemaReactions):
- 主动模式:声明
target字段路径,配合when/fulfill/otherwise控制目标字段的状态或 Schema;target支持 FormPathPattern 匹配路径语法(不支持相对路径),也可通过effects指定独立生命周期钩子(onFieldInit、onFieldMount、onFieldValueChange、onFieldValidateEnd等十余种); - 被动模式:声明
dependencies依赖字段列表,条件满足时更新自身;依赖可以是字符串数组(取依赖字段的 value)、对象数组(可用name起别名、property指定依赖属性,如source#modified写法)或对象格式。
一个典型的被动联动示例——当输入框内容为'123'时展示提示文字:
{ "type": "object", "properties": { "source": { "type": "string", "x-component": "Input" }, "target": { "type": "string", "x-component": "Input", "x-reactions": { "dependencies": ["source"], "fulfill": { "schema": { "x-visible": "{{$deps[0] === '123'}}" } } } } } }x-reactions还支持数组、函数形式("x-reactions": "{{myReaction}}",由作用域注入外部响应器函数实现复杂联动)、fulfill.run直接执行$form.setFieldState(...)语句,以及通过带路径的 Key 精确操作组件属性(如"component[1].style.color")等写法,详见 schema.md 联动用例。
源码视角:JSON 是如何变成字段的
在 RecursionField.ts 中可以看到 JSON Schema 模式的完整执行链:
- Schema 实例化:
new Schema(schemaProp)将普通 JSON 包装为 Schema 树(markRaw包装避免 Vue 过度响应化); - 协议到字段属性:
schema.toFieldProps({ ...options, scope })将 Schema 节点映射为字段工厂属性(映射关系即上文属性表,实现于 @formily/json-schema 的 schema.ts); - 按 type 分发:
object/array/void分别渲染ObjectField/ArrayField/VoidField,其余类型渲染通用Field; - 递归渲染:通过
Schema.getOrderProperties按x-index排序后遍历properties,对每个子节点再次创建RecursionField完成递归;mapProperties/filterProperties可在遍历时改写或过滤子 Schema,onlyRenderProperties控制只渲染子级,x-slot则决定子节点落入哪个插槽。
在SchemaField组件内部(SchemaField.ts),传入的schema若已是Schema实例则直接复用,否则按type: 'object'包装;随后把合并后的组件集合、表达式作用域通过provide注入,最终将 props 连同 schema 一并交给RecursionField。也就是说,JSON Schema 模式与 Markup Schema 模式最终走的是同一条RecursionField渲染管线,这也保证了两种模式渲染结果的一致性。
此外,Schema 类还提供了一套可编程 API(见 schema.md 方法章节),在 JSON Schema 模式之外也可以手动使用:
- 结构操作:
addProperty/removeProperty/setProperties/setItems/addPatternProperty等,用于动态增删改 Schema 节点; - 遍历与转换:
mapProperties/reduceProperties(均按x-index顺序)、fromJSON/toJSON; - 协议兼容:
registerPatches/registerPolyfills/enablePolyfills(['1.0'])可注册协议补丁,兼容 formily 1.x 的x-props、x-linkages、x-rules等旧写法;registerVoidComponents/registerTypeDefaultComponents可声明虚拟组件与类型默认组件。
实战建议与局限
- 适合"配置驱动"场景:当表单结构来自接口返回、低代码平台配置或后端动态下发时,JSON Schema 模式是最佳选择——模板中只有一行
<SchemaField :schema="schema" />,后续增删字段、调整校验与联动全部在 JSON 层完成; - 组件注册是前提:
x-component/x-decorator引用的每个 Key 都必须在createSchemaField({ components })中注册,否则字段无法渲染,这是最常见的报错来源; $ref仅支持本地定义:$ref指定的预定义格式必须形如#/definitions/address,不支持加载远程 JSON Schema(schema.md 详细说明);- 表达式有编译边界:字符串表达式不能包含复杂语句,复杂逻辑应放入作用域注入的函数中(如
x-reactions的函数形式或fulfill.run语句); - 嵌套结构照常工作:
properties嵌套 object、items定义数组(可配合ArrayTable等数组组件)、void类型用于纯布局节点,RecursionField会逐层递归处理,无需额外模板。
如需进一步对比 Markup Schema 的写法、了解ISchema的完整类型定义,可继续阅读 schema-field.md 与 schema.md,并参考同目录下的 recursion-field.vue 用例(演示了RecursionField在自定义组件中手动递归渲染 Schema 的进阶用法)。
- 前端
- UI组件
【免费下载链接】formily
📱🚀 🧩 Cross Device & High Performance Normal Form/Dynamic(JSON Schema) Form/Form Builder -- Support React/React Native/Vue 2/Vue 3
相关推荐
Formily SchemaField 组件详解:JSON Schema 动态表单渲染的完整实战指南
Formily SchemaField 组件详解:JSON Schema 动态表单渲染的完整实战指南 导读 SchemaField 是 Formily Reac
前端UI组件Formily 动态表单核心组件 SchemaField 完全指南:Markup Schema 与 JSON Schema 双模式实战
Formily 动态表单核心组件 SchemaField 完全指南:Markup Schema 与 JSON Schema 双模式实战 SchemaField
前端UI组件Formily动态表单引擎:JSON Schema极速开发
Formily动态表单引擎:JSON Schema极速开发 你是否还在为前后端表单数据对接而烦恼?是否因频繁修改表单结构导致大量重复编码?本文将带你掌握Form
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考