ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Formily React 上下文体系全解析:从 FormContext 到 SchemaOptionsContext 的个性化定制指南

Formily React 上下文体系全解析:从 FormContext 到 SchemaOptionsContext 的个性化定制指南 前端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 实现formily/react中一套精心设计的 React Context 构成了整个表单内核formily/core与 JSX 视图层之间的桥梁。无论是获取当前表单实例、读取当前字段对象还是收集 JSX Markup 写法并转换为标准 JSON Schema都依赖这些上下文。本文以官方 API 文档 context.md 为主线结合仓库源码逐层拆解每一个 Context 的职责、签名、提供者与消费方式帮助你在做复杂个性化定制自定义组件、自定义渲染器、深度封装 SchemaField时有的放矢。一、Context 体系总览在 packages/react/src/shared/context.ts 中官方一共定义了 7 个 Context官方文档列出 6 个另有 1 个SchemaComponentsContext可在源码中找到全部通过createContext创建默认值均为nullContext 名称类型默认值主要用途文档是否收录FormContextFormnull获取当前 Form 实例✅FieldContextGeneralFieldnull获取当前字段实例✅SchemaMarkupContextSchemanull收集 JSX Markup 写法的 Schema 标签并转换为 JSON Schema✅SchemaContextSchemanull获取当前字段的 Schema 信息✅SchemaExpressionScopeContextanynullSchema 表达式作用域✅SchemaOptionsContextISchemaFieldReactFactoryOptionsnull获取createSchemaField传入的全局参数✅SchemaComponentsContextSchemaReactComponentsnull字符串到组件的映射表源码补充❌源码可见此外同一文件还导出了一个ContextCleaner工具组件用于在进入FormProvider子树时把除FormContext之外的上下文统一重置为undefined避免组件在非表单环境中误读到“脏”数据其实现基于createContextCleaner对多个 Context 的Provider进行折叠export const ContextCleaner createContextCleaner( FieldContext, SchemaMarkupContext, SchemaContext, SchemaExpressionScopeContext, SchemaComponentsContext, SchemaOptionsContext )所有 Context 均从包入口 packages/react/src/index.ts 通过export * from ./shared对外导出因此你可以直接import { FormContext } from formily/react使用也可以通过官方提供的useForm、useField、useFieldSchema、useExpressionScope等 Hook 间接消费。二、FormContext获取当前 Form 实例FormContext是整个表单树的根上下文承载着由createForm()创建的Form实例是任何需要访问表单状态values、errors、submit、setValues等的自定义组件的入口。签名见 context.tsimport { Form } from formily/core const FormContext createContextForm(null)提供者FormProviderFormProvider.tsx 是唯一的 Provider 组件它先用useAttach挂载表单生命周期再以ContextCleaner包裹FormContext.Providerexport const FormProvider: ReactFCIProviderProps (props) { const form useAttach(props.form) return ( ContextCleaner FormContext.Provider value{form}{props.children}/FormContext.Provider /ContextCleaner ) }消费方式useFormHook官方封装的 useForm.ts 就是对useContext(FormContext)的直接包装export const useForm T extends object any(): FormT { return useContext(FormContext) }典型的自定义组件用法import React from react import { FormProvider, useForm } from formily/react import { createForm } from formily/core const CustomComponent () { const form useForm() return ( button onClick{() form.submit(console.log)} 提交当前字段数{Object.keys(form.values).length} /button ) } export default () { const form createForm() return ( FormProvider form{form} CustomComponent / /FormProvider ) }需要特别说明的是若自定义组件需要拿到“最近的祖先表单”官方推荐使用 useParentForm.ts 中封装的useParentForm它会优先向上查找最近的ObjectField父级找不到才回退到useForm()得到的表单实例这在嵌套子表单如数组项内的独立表单场景下尤其有用。三、FieldContext获取当前字段实例FieldContext保存的是当前渲染位置所属的字段实例类型为GeneralField即Field、ObjectField、ArrayField、VoidField的联合类型通过它可以在自定义组件内部读取field.value、field.title、field.path、field.selfErrors等字段级状态。签名见 context.tsimport { GeneralField } from formily/core const FieldContext createContextGeneralField(null)提供者四大字段组件FieldContext.Provider由以下四个组件在渲染各自字段时写入Field.tsx普通字段ObjectField.tsx对象字段ArrayField.tsx数组字段VoidField.tsx虚拟字段以Field为例return ( FieldContext.Provider value{field} ReactiveField field{field}{props.children}/ReactiveField /FieldContext.Provider )消费方式useFieldHookuseField.ts 同样是一行包装export const useField T GeneralField(): T { return useContext(FieldContext) as any }在 field.spec.tsx 测试中可以找到真实消费案例自定义组件直接读取useField().path作为 DOM 的data-testidconst Custom () { return div>const SchemaMarkupContext createContextSchema(null)提供者与收集机制SchemaField / MarkupRender在 SchemaField.tsx 中createSchemaField生成的SchemaField组件在渲染前先通过renderMarkup()阶段执行 JSX 收集const renderMarkup () { env.nonameId 0 if (props.schema) return null return render( SchemaMarkupContext.Provider value{schema} {props.children} /SchemaMarkupContext.Provider ) }SchemaField.Markup以及String、Object、Array、Boolean、Number、Date、DateTime、Void等快捷类型最终都走内部的MarkupRender它通过useContext(SchemaMarkupContext)拿到父级 Schema 实例然后按父级类型调用parent.addProperty(name, props)object/void 类型或parent.setItems(schema)/appendArraySchemaarray 类型把当前标签挂载为子节点并把新生成的子 Schema 继续通过SchemaMarkupContext.Provider下发实现递归收集function MarkupRender(props: any) { const parent useContext(SchemaMarkupContext) if (!parent) return Fragment / if (parent.type object || parent.type void) { const schema parent.addProperty(props.name, props) return ( SchemaMarkupContext.Provider value{schema} {renderChildren()} /SchemaMarkupContext.Provider ) } // array 分支parent.setItems / appendArraySchema ... }该机制在 schema.markup.spec.tsx 中有大量测试覆盖包括x-content、x-component等属性的收集与渲染。理解这一层有助于你弄清楚“JSX Markup 与 JSON Schema 两种写法为何等价”这一核心原理。五、SchemaContext当前字段的 Schema 信息SchemaContext保存的是“当前字段”对应的Schema实例来自formily/json-schema用于在自定义组件内读取字段的 schema 元信息如schema.title、schema.type、schema[x-component]、schema.properties是编写递归渲染器RecursionField 式组件的关键上下文。签名见 context.tsconst SchemaContext createContextSchema(null)提供者RecursionFieldRecursionField.tsx 在每次递归渲染前把当前子 Schema 注入SchemaContextreturn ( SchemaContext.Provider value{fieldSchema} {render()} /SchemaContext.Provider )消费方式useFieldSchemaHookuseFieldSchema.ts 直接消费该上下文export const useFieldSchema (): Schema { return useContext(SchemaContext) }在 schema.markup.spec.tsx 的recursion field测试中自定义对象组件CustomObject正是通过useFieldSchema()拿到自身 schema再用RecursionField schema{schema} /渲染其子节点配合onlyRenderProperties属性还可以只渲染子属性而不渲染自身这构成了自定义布局组件的标准范式const CustomObject2: React.FC () { const field useField() const schema useFieldSchema() return ( RecursionField name{schema.name} basePath{field.address} schema{schema} onlyRenderProperties / ) }六、SchemaExpressionScopeContextSchema 表达式作用域JSON Schema 中的表达式x-reactions依赖、x-component-props里的模板表达式等在编译求值时需要一个“作用域”SchemaExpressionScopeContext就是作用域对象的载体类型为any默认null。签名见 context.tsexport const SchemaExpressionScopeContext createContextany(null)提供者ExpressionScope 组件ExpressionScope.tsx 是核心 Provider它读取外层作用域并通过lazyMerge与当前传入值合并后下发因此作用域天然支持多层叠加export const ExpressionScope: ReactFCIExpressionScopeProps (props) { const scope useContext(SchemaExpressionScopeContext) return ( SchemaExpressionScopeContext.Provider value{lazyMerge(scope, props.value)} {props.children} /SchemaExpressionScopeContext.Provider ) }RecordScope提供$record、$index、$lookup与RecordsScope在底层也复用ExpressionScope注入作用域见 RecordScope.tsx。消费方式useExpressionScopeHookuseExpressionScope.ts 直接返回当前作用域对象。官方文档 useExpressionScope.md 给出了作用域的三种来源createSchemaField顶层传入、SchemaField组件属性传入、以及自定义组件内部由ExpressionScope/RecordScope/RecordsScope下发。一个完整的消费示例import React from react import { createForm } from formily/core import { FormProvider, createSchemaField, useExpressionScope, RecordScope, } from formily/react const form createForm() const Custom () { const scope useExpressionScope() return ( code pre{JSON.stringify(scope, null, 2)}/pre /code ) } const SchemaField createSchemaField({ components: { Custom }, scope: { topScope: { aa: 123 } }, }) export default () ( FormProvider form{form} RecordScope getRecord{() ({ name: Record Name, code: Record Code })} getIndex{() 2} SchemaField scope{{ propsScope: { bb: 321 } }} SchemaField.String namecustom x-componentCustom / /SchemaField /RecordScope /FormProvider )RecursionField在把 schema 转换为字段 props 时也会读取该作用域并传给schema.toFieldProps({ scope })见 RecursionField.tsx这也是x-reactions表达式能访问作用域变量的底层原因。七、SchemaOptionsContextSchemaField 工厂全局参数SchemaOptionsContext保存createSchemaField(options)传入的全局参数类型为ISchemaFieldReactFactoryOptions目前包含两个字段见 types.tsexport interface ISchemaFieldReactFactoryOptions Components extends SchemaReactComponents any { components?: Components // 组件映射表 scope?: any // 全局表达式作用域 }签名见 context.tsconst SchemaOptionsContext createContextISchemaFieldReactFactoryOptions(null)提供者createSchemaField 生成的 SchemaField在 SchemaField.tsx 中createSchemaField会把工厂参数与组件级参数同时下发return ( SchemaOptionsContext.Provider value{options} SchemaComponentsContext.Provider value{lazyMerge(options.components, props.components)} ExpressionScope value{lazyMerge(options.scope, props.scope)} {renderMarkup()} {renderChildren()} /ExpressionScope /SchemaComponentsContext.Provider /SchemaOptionsContext.Provider )可见components与scope都支持“工厂级默认 组件级覆盖”的合并策略。需要说明的是文档列出的SchemaOptionsContext主要用于获取工厂参数而源码中同级的SchemaComponentsContextcontext.ts则负责把 schema 中x-component/x-decorator声明的字符串映射到真实组件ReactiveField.tsx 中FormPath.getIn(components, target)正是通过它完成字符串路径如Input.Password到组件的解析两者配合实现了 JSON Schema 驱动的组件注册机制。八、基于 Context 体系的二次定制实战掌握以上上下文后最典型的落地场景是在自定义组件内同步感知字段状态。由于FormContext、FieldContext的值由 Formily 的响应式模型驱动配合formily/react导出的observer详见 observer.md可以实现字段变化自动重渲染的个性化组件import React from react import { observer } from formily/react import { useField, useForm } from formily/react export const CustomLabel observer(() { const field useField() const form useForm() return ( div span字段路径{field.path.toString()}/span span字段标题{field.title}/span span错误信息{(field.selfErrors || []).join(, )}/span span表单校验中{String(form.submitting)}/span /div ) })若要进一步深入建议配合阅读以下仓库文档与源码文档useForm.md、useField.md、useFieldSchema.md、useExpressionScope.md、useParentForm.md上下文定义packages/react/src/shared/context.ts提供者组件FormProvider.tsx、SchemaField.tsx、RecursionField.tsx、ExpressionScope.tsx消费 Hookpackages/react/src/hooks测试用例field.spec.tsx、schema.markup.spec.tsx、expression.spec.tsx九、小结formily/react的 Context 体系是“内核模型 ↔ 视图层”之间的标准通信协议FormContext与FieldContext提供实例访问SchemaContext与SchemaMarkupContext支撑 Schema 的双向转换SchemaExpressionScopeContext与SchemaOptionsContext完成表达式作用域与全局参数的传递SchemaComponentsContext则补齐了字符串组件映射的能力。理解这些上下文的提供者与消费链路是在 Formily 上做复杂个性化定制自定义组件、递归渲染、多级作用域的基础也是阅读其他 API 文档时最值得优先掌握的一环。赞分享前端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 useForm Hook 完全指南从 Form 上下文读取表单实例Formily React useForm Hook 完全指南从 Form 上下文读取表单实例 导读 useForm 是 Formily React 中用于在前端UI组件Formily React 快速上手从安装到实现响应式表单绑定formily/react 实战指南Formily React 快速上手从安装到实现响应式表单绑定formily/react 实战指南 导读 本文基于 Formily 开源仓库中 pack前端UI组件Formily 的 React 渲染层 formily/react从 ViewModel 绑定到 JSON Schema 协议驱动的完整指南Formily 的 React 渲染层 formily/react从 ViewModel 绑定到 JSON Schema 协议驱动的完整指南 formil前端UI组件创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表