Formily Vue 中 SchemaField 的 JSON Schema 模式:直接传入 Schema 对象动态渲染表单
2026/9/24 15:33:16 网站建设 项目流程
  • 前端
  • UI组件

【免费下载链接】formily

📱🚀 🧩 Cross Device & High Performance Normal Form/Dynamic(JSON Schema) Form/Form Builder -- Support React/React Native/Vue 2/Vue 3

项目地址:https://gitcode.com/gh_mirrors/fo/formily
点击查看免费下载

导读

本文聚焦 @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 动态渲染表单的组件,它存在两种等价的使用形态:

  1. Markup Schema 模式:在SchemaField内部嵌套SchemaStringFieldSchemaObjectField等标记组件,以"模板写协议"的方式声明 Schema(详见 schema-field.md)。
  2. 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 \| datetimeGeneralField
title标题stringtitle
description描述stringdescription
default默认值anyinitialValue
readOnly是否只读booleanreadOnly
writeOnly是否只写booleaneditable
enum枚举SchemaEnumdataSource
const / multipleOf / maximum / minimum / maxLength / minLength / pattern / maxItems / minItems / uniqueItems / maxProperties / minProperties / required / format校验规则number \| string \| booleanvalidator
properties对象属性描述Record<string, ISchema>-
items数组项描述ISchema \| ISchema[]-
additionalItems / patternProperties / additionalProperties扩展描述Schema-
x-indexUI 展示顺序number-
x-patternUI 交互模式FieldPatternTypespattern
x-displayUI 展示FieldDisplayTypesdisplay
x-validator字段校验器FieldValidatorvalidator
x-decorator字段 UI 包装器组件string \| 组件decorator
x-decorator-props包装器组件属性anydecorator
x-component字段 UI 组件string \| 组件component
x-component-propsUI 组件属性anycomponent
x-reactions字段联动协议SchemaReactionsreactions
x-content字段内容(子节点)anyReactChildren
x-visible / x-hidden / x-disabled / x-editable / x-read-only / x-read-pretty展示与交互状态booleanvisible / hidden / disabled / editable / readOnly / readPretty
definitionsSchema 预定义Record<string, ISchema>-
$ref引用预定义并合并string-
x-data扩展属性objectdata

在 JSON Schema 模式下,这份表格中除titledefaultrequiredenum等标准 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 的每个属性都可以使用字符串表达式,约定为{{开头、}}结尾的字符串即视为表达式片段。表达式变量可以从createSchemaFieldscope传入,也可以从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指定独立生命周期钩子(onFieldInitonFieldMountonFieldValueChangeonFieldValidateEnd等十余种);
  • 被动模式:声明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 模式的完整执行链:

  1. Schema 实例化new Schema(schemaProp)将普通 JSON 包装为 Schema 树(markRaw包装避免 Vue 过度响应化);
  2. 协议到字段属性schema.toFieldProps({ ...options, scope })将 Schema 节点映射为字段工厂属性(映射关系即上文属性表,实现于 @formily/json-schema 的 schema.ts);
  3. 按 type 分发object/array/void分别渲染ObjectField/ArrayField/VoidField,其余类型渲染通用Field
  4. 递归渲染:通过Schema.getOrderPropertiesx-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-propsx-linkagesx-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

项目地址:https://gitcode.com/gh_mirrors/fo/formily
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询