Formily RecordsScope 使用指南:向 Schema 表达式注入 $records 记录列表作用域
2026/9/23 9:48:49 网站建设 项目流程

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/formily

RecordsScope 是 Formily 的 @formily/react 包提供的一个标准作用域注入组件,专门用于向表单 Schema 的表达式与联动逻辑中注入$records内置变量(当前记录列表数据)。本文以 RecordsScope.md 文档为骨架,结合 @formily/react 的源码实现与测试用例,讲解其作用机制、使用约定,并给出可直接复制运行的自定义列表组件扩展用例,帮助你在自增列表、表格、卡片等动态列表场景中灵活读取整份记录数据。

RecordsScope 是什么:标准作用域注入组件

在 Formily 的 React 架构中,SchemaField渲染的 Schema 节点支持在x-component-propsx-valuex-reactions等位置书写{{...}}模板字符串表达式,表达式求值时依赖一组"作用域变量"。这些变量通过 React Context 逐层下发,形成了一条作用域链路。作用域注入组件(Scope Injection Component)就是在自定义组件内部主动向这条链路"下发"特定变量的组件。

RecordsScope正是这类组件中的标准一员,它负责下发:

  • $records—— 当前记录列表数据(当前 Record 数组)

对应的组件签名如下(见 RecordsScope.md 与 types.ts):

interface IRecordsScopeProps { getRecords(): any[] } type RecordsScope = React.FC<React.PropsWithChildren<IRecordsScopeProps>>

也就是说,它接收一个getRecords函数属性,返回值即为注入到子级作用域的$records列表。

需要说明的是:文档中给出的签名以React.FC展示;从源码看,实际实现基于ReactFC(@formily/react 内部对函数组件的类型别名),行为与普通函数组件完全一致。

源码实现:一个 getter 注入

RecordsScope 的实现非常精简,完整源码位于 RecordsScope.tsx:

import React from 'react' import { ExpressionScope } from './ExpressionScope' import { ReactFC, IRecordsScopeProps } from '../types' export const RecordsScope: ReactFC<IRecordsScopeProps> = (props) => { return ( <ExpressionScope value={{ get $records() { return props.getRecords?.() ?? [] }, }} > {props.children} </ExpressionScope> ) }

这里有三个值得注意的实现细节:

  1. 基于 ExpressionScope 组合:RecordsScope 内部直接复用了 ExpressionScope。ExpressionScope 通过SchemaExpressionScopeContext(Context Provider)把value合并进作用域对象,因此 RecordsScope 注入的$records可以被其子树中所有 Schema 表达式访问到。
  2. Getter 惰性求值$records被定义为 getter,表达式真正求值时才会调用props.getRecords(),因此每次取值都能拿到"当时"的列表数据,而非注入时刻的快照。这对动态列表尤为重要——当数组增删后,表达式能读到最新的记录列表。
  3. 空值兜底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-valuex-reactionsx-component-props中直接书写$records表达式。
  • 约定一致:任何第三方扩展的列表组件,只要遵循"内部使用 RecordsScope"的约定,就能保证与 Formily 官方组件行为一致,表达式编写体验统一。

作用域链路:$records 如何到达字段表达式

在 Formily 的 Schema 渲染链路中,作用域变量的下发链条大致为:

  1. FormProvider提供表单上下文;
  2. SchemaField(由createSchemaField创建)接收 Schema 并进行渲染;
  3. 自定义组件内部通过ExpressionScope(或其衍生组件 RecordScope / RecordsScope)下发额外变量;
  4. 字段表达式在求值阶段,从当前上下文中读取这些变量。

这一机制在 @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.md:

import 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> )

逐段拆解

  1. 自定义组件内部下发(核心步骤):

    const MyCustomComponent = (props) => { return ( <RecordsScope getRecords={() => props.records}> {props.children} </RecordsScope> ) }

    组件接收外部传入的records数组属性,通过getRecords={() => props.records}将其作为$records注入作用域,并渲染props.children。这里的子级即 Schema 中嵌套的字段节点。

  2. 注册到 SchemaField

    const SchemaField = createSchemaField({ components: { Input, MyCustomComponent, }, })

    自定义组件与内置组件一样,通过createSchemaFieldcomponents映射注册,之后便可在 Schema 中用'x-component': 'MyCustomComponent'引用。

  3. Schema 中传数据 + 嵌套字段消费

    • 外层records字段为type: 'void'(不产生表单值,仅承担结构作用),x-component指定为自定义组件,并通过x-component-props.records传入记录数组[{ name: 'Name', code: 'Code' }]
    • 嵌套的input字段使用'x-value': '{{${$records[0].name} ${$records[0].code}}}'在表达式中按索引访问$records第一条记录的namecode
  4. 渲染结果:页面加载后,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.tsx
  • Schema 编译期$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),仅供参考

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

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

立即咨询