Formily RecordsScope 使用指南:向 Schema 表达式注入 $records 记录列表作用域

发布时间:2026/9/23 9:49:07
Formily RecordsScope 使用指南:向 Schema 表达式注入 $records 记录列表作用域
Formily RecordsScope 使用指南向 Schema 表达式注入 $records 记录列表作用域【免费下载链接】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/formilyRecordsScope 是 Formily 的 formily/react 包提供的一个标准作用域注入组件专门用于向表单 Schema 的表达式与联动逻辑中注入$records内置变量当前记录列表数据。本文以 RecordsScope.md 文档为骨架结合 formily/react 的源码实现与测试用例讲解其作用机制、使用约定并给出可直接复制运行的自定义列表组件扩展用例帮助你在自增列表、表格、卡片等动态列表场景中灵活读取整份记录数据。RecordsScope 是什么标准作用域注入组件在 Formily 的 React 架构中SchemaField渲染的 Schema 节点支持在x-component-props、x-value、x-reactions等位置书写{{...}}模板字符串表达式表达式求值时依赖一组作用域变量。这些变量通过 React Context 逐层下发形成了一条作用域链路。作用域注入组件Scope Injection Component就是在自定义组件内部主动向这条链路下发特定变量的组件。RecordsScope正是这类组件中的标准一员它负责下发$records—— 当前记录列表数据当前 Record 数组对应的组件签名如下见 RecordsScope.md 与 types.tsinterface IRecordsScopeProps { getRecords(): any[] } type RecordsScope React.FCReact.PropsWithChildrenIRecordsScopeProps也就是说它接收一个getRecords函数属性返回值即为注入到子级作用域的$records列表。需要说明的是文档中给出的签名以React.FC展示从源码看实际实现基于ReactFCformily/react 内部对函数组件的类型别名行为与普通函数组件完全一致。源码实现一个 getter 注入RecordsScope 的实现非常精简完整源码位于 RecordsScope.tsximport React from react import { ExpressionScope } from ./ExpressionScope import { ReactFC, IRecordsScopeProps } from ../types export const RecordsScope: ReactFCIRecordsScopeProps (props) { return ( ExpressionScope value{{ get $records() { return props.getRecords?.() ?? [] }, }} {props.children} /ExpressionScope ) }这里有三个值得注意的实现细节基于 ExpressionScope 组合RecordsScope 内部直接复用了 ExpressionScope。ExpressionScope 通过SchemaExpressionScopeContextContext Provider把value合并进作用域对象因此 RecordsScope 注入的$records可以被其子树中所有 Schema 表达式访问到。Getter 惰性求值$records被定义为 getter表达式真正求值时才会调用props.getRecords()因此每次取值都能拿到当时的列表数据而非注入时刻的快照。这对动态列表尤为重要——当数组增删后表达式能读到最新的记录列表。空值兜底props.getRecords?.() ?? []意味着即使未传getRecords也会注入一个空数组[]避免表达式访问$records时抛错。从源码结构看RecordsScope 与同目录下的 RecordScope注入$record、$index、$lookup互为补充RecordScope 面向单条记录RecordsScope 面向整份记录列表二者共同构成 Formily 列表类场景的作用域体系。使用约定ArrayX 系列组件的内部标配文档明确了 RecordsScope 的使用约定任何自增列表扩展组件内部都应该使用 RecordsScope用于传递记录作用域变量。目前已实现该约定的组件包括formily/antd 和 formily/next 中的 ArrayX 系列所有组件。所谓自增列表扩展组件指的是 ArrayCards、ArrayCollapse、ArrayItems、ArrayTable、ArrayTabs 这类渲染数组字段的组件。这些组件内部拿到当前记录列表$records后通过 RecordsScope 下发给子级字段使得子级字段的表达式与联动可以按索引访问整份列表数据。对于使用方而言这一约定带来两个直接收益无需手动注入只要你在 Schema 中使用了 ArrayX 系列组件其内部已经完成$records的下发你可以在嵌套字段的x-value、x-reactions、x-component-props中直接书写$records表达式。约定一致任何第三方扩展的列表组件只要遵循内部使用 RecordsScope的约定就能保证与 Formily 官方组件行为一致表达式编写体验统一。作用域链路$records 如何到达字段表达式在 Formily 的 Schema 渲染链路中作用域变量的下发链条大致为FormProvider提供表单上下文SchemaField由createSchemaField创建接收 Schema 并进行渲染自定义组件内部通过ExpressionScope或其衍生组件 RecordScope / RecordsScope下发额外变量字段表达式在求值阶段从当前上下文中读取这些变量。这一机制在 formily/react 的测试用例中有直接印证。见 schema.markup.spec.tsx 中的records scope用例test(records scope, async () { const form createForm() const SchemaField createSchemaField({ components: { Text: (props) div>get $records() { return field.records },它把字段所属的field.records记录列表暴露为$records。这一点与组件注入形成了互补在数组字段内部$records既可来自 RecordsScope 的显式注入也可来自 Schema 编译器的字段作用域。对应的测试用例见 transformer.spec.ts其中userReactions with $lookup $record $records $index用例演示了{{$self.title $records[$index].b}}这类联动表达式的写法。实战自定义列表组件中注入 $records文档给出的核心示例是自定义组件扩展用例——当你自己封装一个渲染记录列表的组件时应该用 RecordsScope 将列表数据下发给子级字段。完整代码见 RecordsScope.mdimport React from react import { createForm } from formily/core import { FormProvider, createSchemaField, RecordsScope } from formily/react import { Input } from antd const form createForm() const MyCustomComponent (props) { return ( RecordsScope getRecords{() props.records} {props.children} /RecordsScope ) } const SchemaField createSchemaField({ components: { Input, MyCustomComponent, }, }) export default () ( FormProvider form{form} SchemaField schema{{ type: object, properties: { records: { type: void, x-component: MyCustomComponent, x-component-props: { records: [ { name: Name, code: Code, }, ], }, properties: { input: { type: string, x-component: Input, x-value: {{ ${$records[0].name} ${$records[0].code} }}, }, }, }, }, }} /SchemaField /FormProvider )逐段拆解自定义组件内部下发核心步骤const MyCustomComponent (props) { return ( RecordsScope getRecords{() props.records} {props.children} /RecordsScope ) }组件接收外部传入的records数组属性通过getRecords{() props.records}将其作为$records注入作用域并渲染props.children。这里的子级即 Schema 中嵌套的字段节点。注册到 SchemaFieldconst SchemaField createSchemaField({ components: { Input, MyCustomComponent, }, })自定义组件与内置组件一样通过createSchemaField的components映射注册之后便可在 Schema 中用x-component: MyCustomComponent引用。Schema 中传数据 嵌套字段消费外层records字段为type: void不产生表单值仅承担结构作用x-component指定为自定义组件并通过x-component-props.records传入记录数组[{ name: Name, code: Code }]嵌套的input字段使用x-value: {{${$records[0].name} ${$records[0].code}}}在表达式中按索引访问$records第一条记录的name与code。渲染结果页面加载后input字段的初始值会被表达式求值为Name Code。要点与注意事项表达式书写格式文档示例中的{{ ${...} }}是 JS 字符串拼接写法最终生成的表达式为模板字符串形式{{${$records[0].name} ${$records[0].code}}}即双花括号包裹一个模板字符串。等价写法可以直接书写为x-value: {{${$records[0].name} ${$records[0].code}}}getRecords 应保持取当下值语义由于源码中使用 getter 惰性求值getRecords返回的数据应当反映组件渲染时刻的记录列表如果列表数据可能变化例如来自 ArrayField 的动态增删应确保每次调用都返回最新列表而非缓存快照。不传 getRecords 时的兜底源码中props.getRecords?.() ?? []保证缺失时注入空数组表达式仍可安全访问$records如$records.length得到 0。$records与$record的分工单条记录上下文用 RecordScope 的$record/$index整份列表上下文用 RecordsScope 的$records。若二者叠加使用内层作用域会通过lazyMerge与上层作用域合并互不覆盖。结语RecordsScope 是 Formily 列表类场景中记录列表作用域的标准下发入口官方 ArrayX 系列组件内部依赖它第三方扩展列表组件也应遵循同一约定。通过本文的源码拆解与自定义组件用例你可以在自己的组件中轻松注入$records让嵌套字段的表达式、初始值与联动逻辑直接访问整份记录列表数据。相关参考组件实现RecordsScope.tsx、ExpressionScope.tsx、RecordScope.tsx类型定义types.ts测试用例schema.markup.spec.tsxSchema 编译期$records注入transformer.ts 与 transformer.spec.ts中文版文档RecordsScope.zh-CN.md【免费下载链接】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),仅供参考