Formily Vue useFieldSchema 完全指南:在自定义组件中读取当前字段的 Schema 信息
2026/9/23 16:06:43 网站建设 项目流程
  • 前端
  • 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
点击查看免费下载

导读

useFieldSchema是 Formily Vue(@formily/vue)提供的一个内置 Composition API Hook,用于在自定义组件内部读取当前字段所对应的 Schema 描述。在协议驱动的表单架构中,组件与数据模型(Field Model)解耦,组件侧往往需要拿到描述自身"是什么、长什么样"的 JSON Schema 元信息,才能做出正确的渲染与交互决策。读完本文,你将掌握useFieldSchema的签名、注入原理、使用边界,并能结合Schema类的方法(toJSONtoFieldPropscompile等)在SchemaField/RecursionField子树中实现基于 Schema 的自定义渲染组件。

1. 什么是 useFieldSchema:协议驱动场景下的"元信息读取口"

在 Formily 的 Vue 实现中,表单渲染分为两条技术路线:

  • 普通模式(Model 驱动):直接使用FieldObjectFieldArrayFieldVoidField组件与@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')

注意这里有两个容易混淆的注入键:

  • SchemaMarkupSymbolSchemaField组件在解析标记式(Markup)Schema时注入的父级 Schema 引用(见 SchemaField.ts),供SchemaObjectField等标记字段把自身挂载到父 Schema 上;
  • SchemaSymbolRecursionField渲染当前节点时注入的"当前字段 Schema",正是useFieldSchema读取的目标。

注入链:谁 provide,谁 inject

SchemaSymbolprovide发生在 RecursionField.ts:

const fieldSchemaRef = computed(() => createSchema(props.schema)) // ... provide(SchemaSymbol, fieldSchemaRef)

也就是说,每当RecursionField基于一份 schema 递归渲染时,它就会把new Schema(schema)得到的实例以Ref形式注入到子树中。而SchemaField内部最终也是通过渲染RecursionField来下发 Schema 的(见 SchemaField.ts)。因此:

SchemaFieldRecursionField的子树里调用useFieldSchema(),拿到的一定是"最近一层 RecursionField 正在渲染的那个 Schema 节点"。

这条调用链也解释了官方文档中"只能用在SchemaFieldRecursionField子树中"的约束:脱离这两个组件,上下文里没有SchemaSymbolinject就会退回默认的空ref(),此时schemaRef.valueundefined,无法取得有效 Schema。

3. 使用边界与常见误区

基于源码机制,可以总结出以下必须注意的使用边界:

  1. 只能在协议驱动的组件树中使用:组件必须由SchemaField(或其内部的RecursionField)渲染,才能保证SchemaSymbol被注入;
  2. 不要在普通 Model 驱动组件中使用:例如直接放在FormProvider下、与 Schema 无关的纯 UI 组件,取到的将是空引用;
  3. 拿不到就做兜底:因为默认值是ref(),对schemaRef.value的使用前应做空值判断(如v-if="schema"if (!schema) return ...);
  4. 返回的是 Schema 实例而非普通 JSONSchema@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递归解析;
  • 组件内部消费Customsetup中调用useFieldSchema()拿到schemaRef,渲染函数里用schema.toJSON()Schema实例序列化为纯 JSON 并格式化打印。由于schema中嵌套的input字段同样指定了x-component: 'Custom',递归渲染时内层Custom拿到的又是它自己的 Schema 节点——这正是"每个节点读取自己那份协议"的直观体现。

运行后,页面会把当前字段的完整 Schema(包含typepropertiesx-component等)以格式化 JSON 的形式渲染出来。

5. 拿到 Schema 之后:Schema 实例的核心能力

useFieldSchema()返回的Ref<Schema>中,Schema@formily/json-schema提供的通用类,SchemaFieldRecursionField都依赖它。其能力在 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?)——控制表达式编译出错时是否静默。

createSchemaFieldSchemaField组件都支持传入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 ModelRef<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.valueundefined

检查组件是否真的位于SchemaField/RecursionField的子树中,以及该组件是否通过x-component被 Schema 节点引用。脱离协议树渲染时,inject会命中默认值ref()(空引用),这是源码设计使然(见 useFieldSchema.ts)。

Q2:useFieldSchema()拿到的对象为什么不能直接JSON.stringify

因为拿到的是Schema类实例(含原型方法),直接序列化得不到干净的协议数据。应调用schema.toJSON()(见第 5.1 节)。

Q3:想按 Schema 类型做分支渲染,怎么做?

读取schema.value.typeschema.value['x-component']即可。注意 Markup Schema 场景下,x-component必须与createSchemaField({ components })注册的 key 完全匹配,否则组件无法被解析(见 schema.md)。

结语

useFieldSchema是 Formily Vue 协议驱动体系的"元信息读取口":它通过provide/inject机制,把RecursionField当前渲染节点的Schema实例以Ref<Schema>的形式下发给子树,让自定义组件能够"认识自己"。理解它的注入链(SchemaFieldRecursionFieldprovide(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

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

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

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

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

立即咨询