ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Formily Vue 的 injections 上下文注入体系:FormContext 与 Schema 系列注入详解

Formily Vue 的 injections 上下文注入体系:FormContext 与 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点击查看免费下载formily/vue 的 injections 是一套基于 Vue 3 Composition API经 vue-demi 同时兼容 Vue 2的依赖注入上下文集合。它把表单实例、字段实例与 JSON Schema 运行时信息封装成 6 个可消费的注入项是useForm、useField、useFieldSchema等核心 Hook 以及SchemaField递归渲染链路得以工作的底层基石。阅读本文后你将能理解每个注入项的类型签名、provide 来源与消费方式并能在自定义组件与高阶封装中正确使用这些上下文。总览六个注入项与底层 Symbol所有注入项都定义在 packages/vue/src/shared/context.ts每个注入项由一个InjectionKeySymbol与对应的injectT(Symbol)消费表达式组成全部在FormProvider或SchemaField内部通过provide注入注入项类型由谁 provideFormContextRefFormFormProviderFieldContextRefGeneralFieldReactiveFieldSchemaMarkupContextRefSchemaSchemaField/MarkupFieldSchemaContextRefSchemaRecursionFieldSchemaExpressionScopeContextRefRecordstring, anySchemaField/ExpressionScopeSchemaOptionsContextRefISchemaFieldVueFactoryOptionsSchemaField需要注意这些注入值的实际类型都是 Vue 的Ref响应式引用而不是裸实例——因为 Formily 的字段、Schema 都是响应式对象以 Ref 形式提供才能保证消费方随状态变化重新渲染。这一点在 context.ts 中由InjectionKeyRef...的泛型签名明确体现。FormContext获取当前 Form 实例描述Form 上下文可以获取当前 Form 实例。签名import { Form } from formily/core const FormContext injectForm(FormSymbol)FormSymbol的类型为InjectionKeyRefForm。它在 FormProvider.ts 中被提供FormProvider通过useAttach(toRef(props, form))将传入的 form 包装为 Ref 后执行provide(FormSymbol, formRef)。同时FormProvider的 setup 中还会调用useInjectionCleaner清理其余 5 个注入项FieldSymbol、SchemaMarkupSymbol、SchemaSymbol、SchemaExpressionScopeSymbol、SchemaOptionsSymbol确保每个FormProvider边界内的字段级上下文不会串到外层从源码看这是 FormProvider.ts 的固定行为。官方封装好的消费方式是 useForm.tsimport { inject, Ref, ref } from vue-demi import { Form } from formily/core import { FormSymbol } from ../shared/context export const useForm (): RefForm { const form inject(FormSymbol, ref()) return form }inject的第二个参数是默认值在FormProvider之外使用时useForm会返回一个空 Ref不会抛错。因此自定义组件中应优先使用useForm()而非直接inject(FormSymbol)。FieldContext获取当前字段实例描述字段上下文可以获取当前字段实例。签名import { GeneralField } from formily/core const FieldContext injectGeneralField(FieldSymbol)FieldSymbol类型为InjectionKeyRefGeneralFieldGeneralField是Field、ArrayField、ObjectField、VoidField的联合抽象保证在任意字段组件中都能拿到统一的字段接口。它的提供方是 ReactiveField.tsReactiveField的 setup 中会依据formRef与父字段parentRef调用form.createXXXField(...)创建字段实例随后执行provide(FieldSymbol, fieldRef)将字段以 Ref 形式注入子树。ReactiveField本身又被observer包裹因此字段状态变化会驱动依赖该注入项的组件更新。消费侧同样有封装好的 useField.tsimport { inject, Ref, ref } from vue-demi import { GeneralField } from formily/core import { FieldSymbol } from ../shared/context export const useField T GeneralField(): RefT { return inject(FieldSymbol, ref()) as any }通过useField()自定义组件可以获得当前字段实例进而读取field.value、field.display、field.pattern等响应式状态这是编写自定义 Formily 字段组件Custom Component的基础能力。在ReactiveField渲染阶段display ! visible的字段会直接渲染为空的template见 ReactiveField.ts所以依赖FieldContext的组件只会作用于可见字段。SchemaMarkupContext收集 JSX Markup 写法的 Schema 标签描述Schema 标签上下文主要用于收集 JSX Markup 写法的 Schema 标签然后转换成标准 JSON Schema。签名SchemaMarkupContext injectSchema(SchemaMarkupSymbol)这是 Formily「Schema 标签化Markup」能力的关键注入项。SchemaField在 setup 中先provide(SchemaMarkupSymbol, schemaRef)见 SchemaField.ts把当前SchemaField的根 Schema 作为收集容器随后渲染子树中的SchemaMarkupField/SchemaStringField/SchemaObjectField等标签组件。MarkupField的 setup 会通过inject(SchemaMarkupSymbol, null)取到父级 Schema 容器再根据父级类型调用parentRef.value.addProperty(name, schema)object/void 类型或setItems(...)array 类型把标签 props 转换出的标准 JSON Schema 挂载到父 Schema 上见 SchemaField.ts。挂载后MarkupField还会provide(SchemaMarkupSymbol, schemaRef)继续向下传递形成嵌套收集最终这些被收集的properties会交给紧随其后的RecursionField递归渲染SchemaField.ts。MarkupField渲染结果是一个display: none的 divSchemaField.ts即标签本身不可见只承担 Schema 收集职责。因此该上下文主要面向 Schema 内部渲染机制用户一般无需直接消费但如果要自定义 Schema 收集行为它就是唯一的注入入口。SchemaContext获取当前字段的 Schema 信息描述字段 Schema 上下文主要用于获取当前字段的 Schema 信息。签名const SchemaContext injectSchema(SchemaSymbol)SchemaSymbol类型为InjectionKeyRefSchemaSchema来自formily/json-schema。提供方是 RecursionField.tsRecursionField会把props.schema通过new Schema(schemaProp)实例化markRaw包装避免不必要的响应式代理并执行provide(SchemaSymbol, fieldSchemaRef)保证其子树内每个组件都能读到当前层级的 Schema。官方消费封装为 useFieldSchema.tsimport { inject, ref } from vue-demi import { SchemaSymbol } from ../shared/context export const useFieldSchema () { return inject(SchemaSymbol, ref()) }结合useField与useFieldSchema自定义组件可以同时拿到「字段实例」与「Schema 描述」例如读取schema[x-component-props]中的配置来定制渲染行为。RecursionField的getPropsFromSchema调用schema.toFieldProps(...)时还会合并SchemaOptionsContext与SchemaExpressionScopeContext作为表达式作用域见 RecursionField.ts这也是x-reactions等表达式能解析到外部变量的底层原因之一。SchemaExpressionScopeContextSchema 表达式作用域描述Schema 表达式作用域上下文。签名const SchemaExpressionScopeContext injectany(SchemaExpressionScopeSymbol)SchemaExpressionScopeSymbol实际类型为InjectionKeyRefRecordstring, any见 context.ts。它承载 Schema 内x-*表达式如x-visible、x-reactions、x-component-props中的函数/表达式可以访问的外部变量集合。提供方有两处SchemaField在 setup 中通过computed(() lazyMerge(options.scope, props.scope))合并工厂参数 scope 与组件 props 传入的 scope 后provideSchemaField.tsExpressionScope.ts 组件会读取当前作用域并与自己的valueprop 做lazyMerge再向下provide实现局部作用域的叠加与覆盖。RecursionField中toFieldProps的scopegetter 也是将optionsRef.value.scope与scopeRef.value合并RecursionField.ts与SchemaField的合并逻辑一致。需要强调的是lazyMerge是浅层合并因此ExpressionScope覆盖同名 key 时是整值替换而非深度合并。SchemaOptionsContextcreateSchemaField 传入的全局参数描述Schema 全局参数上下文主要用于获取从 createSchemaField 传入的参数。签名const SchemaOptionsContext injectISchemaFieldVueFactoryOptions(SchemaOptionsSymbol)ISchemaFieldVueFactoryOptions定义在 packages/vue/src/types/index.tsexport interface ISchemaFieldVueFactoryOptions Components extends SchemaVueComponents any { components?: Components scope?: any }即createSchemaField({ components, scope })的工厂参数。SchemaField的 setup 中会把options与 props 传入的components合并为optionsRef并provide(SchemaOptionsSymbol, optionsRef)SchemaField.ts。RecursionField通过inject(SchemaOptionsSymbol)拿到该上下文在schema.toFieldProps时把components用于解析x-component/x-decorator对应的注册组件scope用于表达式求值RecursionField.ts。ReactiveField渲染时也用FormPath.getIn(options?.components, field.componentType)从组件表中查找最终组件ReactiveField.ts。因此修改createSchemaField传入的components会全局影响所有 Schema 字段的组件解析这是 Formily Vue 组件注册体系的核心入口。典型消费场景自定义组件中组合使用六个注入项并非孤立存在实践中常组合使用。以官方封装 Hook 为参照import { useForm, useField, useFieldSchema } from formily/vue // 自定义字段组件内部 export default { name: MyField, setup() { const formRef useForm() // FormContext - RefForm const fieldRef useField() // FieldContext - RefGeneralField const schemaRef useFieldSchema() // SchemaContext - RefSchema return () { const form formRef.value const field fieldRef.value const schema schemaRef.value // 读取 field.display / field.value / schema[x-component-props] 等 // 调用 form.submit / form.setValuesIn 等 } }, }使用前提useForm必须在FormProvider :formform内useField/useFieldSchema必须在ReactiveField即任何字段组件或由SchemaField递归渲染的子树内。在FormProvider外调用useForm会得到默认空 Ref属于可容忍的降级行为而useField在非字段环境同样返回空 Ref需要自行判空。与其他包注入体系的对应关系注入体系在 Formily 的 React 与 Vue 实现中结构对齐formily/react 使用FormContext、FieldContext、SchemaMarkupContext、SchemaContext、SchemaExpressionScopeContext、SchemaOptionsContext这套同名字段。Vue 版本通过 vue-demi 同时支持 Vue 2Options API 渲染函数与 Vue 3例如ReactiveField中isVue2分支会额外用reaction包裹字段创建逻辑ReactiveField.ts这是响应式差异的兼容处理。理解本注入体系也就能快速迁移到 React 版本的相同心智模型。小结6 个注入项均定义于 packages/vue/src/shared/context.ts类型全部为RefTFormContext由FormProvider提供FieldContext由ReactiveField提供两者是表单/字段实例的获取入口SchemaMarkupContext、SchemaContext、SchemaExpressionScopeContext、SchemaOptionsContext由SchemaField/RecursionField/ExpressionScope协作提供支撑 Markup 收集、Schema 读取、表达式作用域与组件解析四条链路日常开发优先使用useForm、useField、useFieldSchema三个封装 Hook它们对缺失注入做了默认值降级需要深度定制如自定义 Schema 收集、局部表达式作用域时可直接inject对应 Symbol并参考 ExpressionScope 与 SchemaField 的实现模式。赞分享前端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 React RecordsScope 详解在 JSON Schema 表达式中注入 $records 记录列表作用域Formily React RecordsScope 详解在 JSON Schema 表达式中注入 $records 记录列表作用域 导读 RecordsSc前端UI组件Formily ExpressionScope 组件详解为 JSON Schema 表达式注入局部作用域Formily ExpressionScope 组件详解为 JSON Schema 表达式注入局部作用域 ExpressionScope 是 Formily前端UI组件Formily RecordsScope 使用指南向 Schema 表达式注入 $records 记录列表作用域Formily RecordsScope 使用指南向 Schema 表达式注入 $records 记录列表作用域 RecordsScope 是 Formily前端UI组件创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表