- 前端
- UI组件
【免费下载链接】formily
📱🚀 🧩 Cross Device & High Performance Normal Form/Dynamic(JSON Schema) Form/Form Builder -- Support React/React Native/Vue 2/Vue 3
导读
useFieldSchema是 Formily Vue(@formily/vue)提供的一个内置 Composition API Hook,用于在自定义组件内部读取当前字段所对应的 Schema 描述。在协议驱动的表单架构中,组件与数据模型(Field Model)解耦,组件侧往往需要拿到描述自身"是什么、长什么样"的 JSON Schema 元信息,才能做出正确的渲染与交互决策。读完本文,你将掌握useFieldSchema的签名、注入原理、使用边界,并能结合Schema类的方法(toJSON、toFieldProps、compile等)在SchemaField/RecursionField子树中实现基于 Schema 的自定义渲染组件。
1. 什么是 useFieldSchema:协议驱动场景下的"元信息读取口"
在 Formily 的 Vue 实现中,表单渲染分为两条技术路线:
- 普通模式(Model 驱动):直接使用
Field、ObjectField、ArrayField、VoidField组件与@formily/core的字段模型交互; - 协议模式(JSON Schema 驱动):通过
SchemaField解析 JSON Schema 动态渲染表单,字段的 UI 形态完全由 Schema 描述。
useFieldSchema正是为协议模式服务的。它的官方定位是:主要在自定义组件中读取当前字段的 Schema 信息,并且只能用在SchemaField或者RecursionField的子树中使用(见 use-field-schema.md)。
通俗地说:当你在createSchemaField中注册了一个自定义组件,并且这个组件被某个x-component: "YourCustom"的 Schema 节点引用时,该组件内部就可以通过useFieldSchema()拿到这个 Schema 节点本身——也就是描述"我自己"的那份协议数据。
interface useFieldSchema { (): Ref<Schema> }2. 源码实现:一次极简的 provide / inject
useFieldSchema的实现非常简洁,完整源码位于 useFieldSchema.ts:
import { inject, ref } from 'vue-demi' import { SchemaSymbol } from '../shared/context' export const useFieldSchema = () => { return inject(SchemaSymbol, ref()) }它做且只做一件事:从当前组件上下文通过inject取出SchemaSymbol对应的响应式引用,默认值是一个空的ref()。由于返回值是Ref<Schema>,在 Vue 模板或setup中需要通过.value访问实际的Schema实例。
SchemaSymbol的定义位于 context.ts,它是一组 Vue 注入键(InjectionKey)之一:
export const SchemaMarkupSymbol: InjectionKey<Ref<Schema>> = Symbol('schemaMarkup') export const SchemaSymbol: InjectionKey<Ref<Schema>> = Symbol('schema')注意这里有两个容易混淆的注入键:
SchemaMarkupSymbol:SchemaField组件在解析标记式(Markup)Schema时注入的父级 Schema 引用(见 SchemaField.ts),供SchemaObjectField等标记字段把自身挂载到父 Schema 上;SchemaSymbol:RecursionField渲染当前节点时注入的"当前字段 Schema",正是useFieldSchema读取的目标。
注入链:谁 provide,谁 inject
SchemaSymbol的provide发生在 RecursionField.ts:
const fieldSchemaRef = computed(() => createSchema(props.schema)) // ... provide(SchemaSymbol, fieldSchemaRef)也就是说,每当RecursionField基于一份 schema 递归渲染时,它就会把new Schema(schema)得到的实例以Ref形式注入到子树中。而SchemaField内部最终也是通过渲染RecursionField来下发 Schema 的(见 SchemaField.ts)。因此:
在
SchemaField或RecursionField的子树里调用useFieldSchema(),拿到的一定是"最近一层 RecursionField 正在渲染的那个 Schema 节点"。
这条调用链也解释了官方文档中"只能用在SchemaField或RecursionField子树中"的约束:脱离这两个组件,上下文里没有SchemaSymbol,inject就会退回默认的空ref(),此时schemaRef.value为undefined,无法取得有效 Schema。
3. 使用边界与常见误区
基于源码机制,可以总结出以下必须注意的使用边界:
- 只能在协议驱动的组件树中使用:组件必须由
SchemaField(或其内部的RecursionField)渲染,才能保证SchemaSymbol被注入; - 不要在普通 Model 驱动组件中使用:例如直接放在
FormProvider下、与 Schema 无关的纯 UI 组件,取到的将是空引用; - 拿不到就做兜底:因为默认值是
ref(),对schemaRef.value的使用前应做空值判断(如v-if="schema"或if (!schema) return ...); - 返回的是 Schema 实例而非普通 JSON:
Schema是@formily/json-schema提供的类,自带解析、编译、转换等方法(详见下文第 5 节),需要序列化时显式调用toJSON()。
4. 完整用例:在自定义组件中打印当前字段 Schema
原文档通过一个dumi-previewer演示组件挂载了官方 demo,其源码位于 use-field-schema.vue。下面给出完整、可运行的版本并逐段讲解:
<template> <FormProvider :form="form"> <SchemaField> <SchemaObjectField name="custom" x-component="Custom" :x-component-props="{ schema: { type: 'object', properties: { input: { type: 'string', 'x-component': 'Custom', }, }, }, }" /> </SchemaField> </FormProvider> </template> <script> import { defineComponent, h } from '@vue/composition-api' import { createForm } from '@formily/core' import { FormProvider, createSchemaField, useFieldSchema } from '@formily/vue' import 'ant-design-vue/dist/antd.css' const Custom = defineComponent({ setup() { const schemaRef = useFieldSchema() return () => { const schema = schemaRef.value return h( 'div', { style: { whiteSpace: 'pre' }, }, [JSON.stringify(schema.toJSON(), null, 4)] ) } }, }) const { SchemaField, SchemaObjectField } = createSchemaField({ components: { Custom, }, }) export default { components: { FormProvider, SchemaField, SchemaObjectField }, data() { const form = createForm({ validateFirst: true }) return { form, } }, } </script>关键点拆解:
createSchemaField工厂:createSchemaField接受{ components, scope }两个可选参数(签名见 schema-field.md),components中的Custom就是被x-component="Custom"引用的组件;SchemaObjectField标记字段:通过name="custom"与x-component="Custom"声明一个对象类型的标记节点,其x-component-props里携带了一份嵌套的 JSON Schema——这份 Schema 会被RecursionField递归解析;- 组件内部消费:
Custom的setup中调用useFieldSchema()拿到schemaRef,渲染函数里用schema.toJSON()把Schema实例序列化为纯 JSON 并格式化打印。由于schema中嵌套的input字段同样指定了x-component: 'Custom',递归渲染时内层Custom拿到的又是它自己的 Schema 节点——这正是"每个节点读取自己那份协议"的直观体现。
运行后,页面会把当前字段的完整 Schema(包含type、properties、x-component等)以格式化 JSON 的形式渲染出来。
5. 拿到 Schema 之后:Schema 实例的核心能力
useFieldSchema()返回的Ref<Schema>中,Schema是@formily/json-schema提供的通用类,SchemaField与RecursionField都依赖它。其能力在 schema.md 中有完整文档,这里重点列出与"自定义组件读取 Schema"最相关的部分:
5.1 序列化与反序列化
toJSON(): ISchema——把当前Schema对象还原成普通 JSON 数据,是"读取并展示/传递 Schema"最常用的方法(上文 demo 即用此方法);fromJSON(json: ISchema): Schema——把普通 JSON 转成Schema对象;isSchemaInstance(target)——判断一个对象是否为Schema实例。
5.2 转换成字段模型属性
toFieldProps(): IFieldFactoryProps——把当前Schema对象转换成 Formily 字段模型属性(IFieldFactoryProps)。RecursionField内部正是通过schema?.toFieldProps?.({ ...optionsRef.value, scope })来生成字段 props 的(见 RecursionField.ts)。自定义组件如果要做"协议 → 模型"的桥接,可以复用这一方法。
5.3 表达式编译
compile(scope): Schema——深度递归当前 Schema 中的{{expression}}表达式片段并编译,支持传入作用域对象;- 静态方法
Schema.compile(target, scope)/Schema.shallowCompile(target, scope)——编译任意对象中的表达式片段; Schema.registerCompiler(compiler)——注册自定义表达式编译器;Schema.silent(value?)——控制表达式编译出错时是否静默。
createSchemaField与SchemaField组件都支持传入scope(全局 / 局部表达式作用域),两者会被lazyMerge合并后注入(见 SchemaField.ts),供{{$form}}、{{$self}}、{{$values}}等内置作用域变量消费。
5.4 属性树操作
addProperty(key, schema)/removeProperty(key)/setProperties(properties)——增删改子属性描述;addPatternProperty(regexp, schema)/setItems(items)等——操作正则属性与数组项描述;mapProperties(mapper)/reduceProperties(reducer, initialValue?)——按x-index顺序遍历properties。
5.5 常见属性速查
Schema同时承载了 JSON Schema 标准属性与 Formily 扩展属性(x-*系列),自定义组件中常用的包括:
| 属性 | 含义 |
|---|---|
type | 字段类型(string/object/array/number/boolean/void/date/datetime等) |
title/description | 标题与描述 |
default | 默认值,映射为字段initialValue |
enum | 枚举,映射为dataSource |
required/pattern/format等 | 校验规则,映射为validator |
x-component/x-component-props | 字段 UI 组件及其属性 |
x-decorator/x-decorator-props | 字段 UI 包装器组件及其属性 |
x-visible/x-hidden/x-disabled/x-editable/x-read-pretty | 显隐、禁用、只读等交互状态 |
x-reactions | 字段联动协议 |
x-data | 扩展自定义数据 |
完整的属性表、字段模型映射关系与方法签名可查阅 schema.md。
6. 与兄弟 Hooks 的分工:useField / useForm / useParentForm
@formily/vue在 hooks/index.ts 中统一导出了一组 hooks,useFieldSchema与它们互补,构成自定义组件的"上下文三件套":
| Hook | 读取内容 | 典型用途 |
|---|---|---|
useFieldSchema() | 当前字段的Schema 实例(Ref<Schema>) | 读取协议元信息、按协议动态渲染 |
useField() | 当前字段的Field Model(Ref<GeneralField>) | 读取/操作字段值、状态、校验(实现见 useField.ts,读取FieldSymbol) |
useForm() | 最近的Form 实例 | 表单级操作(提交、校验、查询字段) |
useParentForm() | 向上查找的Form 实例 | 在弹窗/抽屉等隔离场景中穿透查找表单 |
useFormEffects() | 表单生命周期副作用 | 订阅字段挂载、值变化等事件 |
一个实用的组合模式是:用useFieldSchema()决定"怎么渲染",用useField()决定"渲染什么数据",两者结合即可写出完全由协议驱动、可高度复用的自定义组件。例如:
setup() { const schemaRef = useFieldSchema() // 当前字段协议 const fieldRef = useField() // 当前字段模型 return () => { const schema = schemaRef.value const field = fieldRef.value if (!schema) return null // 依据 schema['x-component-props'] 与 field.value 定制渲染 } }7. 常见问题排查
Q1:在自定义组件里调用useFieldSchema()后schemaRef.value是undefined?
检查组件是否真的位于SchemaField/RecursionField的子树中,以及该组件是否通过x-component被 Schema 节点引用。脱离协议树渲染时,inject会命中默认值ref()(空引用),这是源码设计使然(见 useFieldSchema.ts)。
Q2:useFieldSchema()拿到的对象为什么不能直接JSON.stringify?
因为拿到的是Schema类实例(含原型方法),直接序列化得不到干净的协议数据。应调用schema.toJSON()(见第 5.1 节)。
Q3:想按 Schema 类型做分支渲染,怎么做?
读取schema.value.type或schema.value['x-component']即可。注意 Markup Schema 场景下,x-component必须与createSchemaField({ components })注册的 key 完全匹配,否则组件无法被解析(见 schema.md)。
结语
useFieldSchema是 Formily Vue 协议驱动体系的"元信息读取口":它通过provide/inject机制,把RecursionField当前渲染节点的Schema实例以Ref<Schema>的形式下发给子树,让自定义组件能够"认识自己"。理解它的注入链(SchemaField→RecursionField→provide(SchemaSymbol)→useFieldSchemainject)、使用边界以及与useField/useForm等 hooks 的分工,你就能写出真正协议驱动、按 Schema 自我描述渲染的高复用表单组件。相关实现与文档可继续阅读:
- Hook 实现:useFieldSchema.ts
- 注入上下文定义:context.ts
- 注入方实现:RecursionField.ts、SchemaField.ts
- 完整 demo:use-field-schema.vue
- Schema 类全量文档:schema.md
- 前端
- UI组件
【免费下载链接】formily
📱🚀 🧩 Cross Device & High Performance Normal Form/Dynamic(JSON Schema) Form/Form Builder -- Support React/React Native/Vue 2/Vue 3
相关推荐
Formily React 中 useFieldSchema Hook 完全指南:在自定义组件中读取当前字段 Schema
Formily React 中 useFieldSchema Hook 完全指南:在自定义组件中读取当前字段 Schema 导读 useFieldSchema
前端UI组件formily 自定义组件读取字段 Schema 指南:useFieldSchema 的原理与实战用法
formily 自定义组件读取字段 Schema 指南:useFieldSchema 的原理与实战用法 在 formily 的 @formily/react 体
前端UI组件Formily React useField Hook 完全指南:在自定义组件中读取字段模型与状态
Formily React useField Hook 完全指南:在自定义组件中读取字段模型与状态 导读 useField 是 Formily React 体系
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考