NocoBase RunJS 深度解析:ctx.collection 数据表实例的元数据访问、主键操作与字段联动实践
【免费下载链接】nocobaseNocoBase is an open-source AI + no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase
ctx.collection是 NocoBase RunJS 运行时中最核心的上下文属性之一,它指向当前 JS 执行上下文关联的数据表(Collection)实例,让开发者无需硬编码表名,就能在运行时动态读取数据表的名称、字段列表、主键(filterTargetKey)、数据源归属与模板类型等元数据。本文基于 官方 ctx.collection 文档,结合 NocoBase 仓库中 RunJS 上下文的实际实现,完整讲解其适用场景、属性方法清单、与ctx.collectionField/ctx.blockModel的关系,以及三个可直接复制的实战示例(打开弹窗传filterByTk、遍历字段做必填校验、获取关联字段构建子表格),读完即可在自己的 JS 区块 / JS 字段中正确使用这一对象。
一、它是什么:上下文关联的数据表实例
在 NocoBase 中,界面(区块)和字段都建立在"数据表(Collection)"之上。当你在 JS 区块(JSBlock)、JS 字段(JSField)、表格列(JSColumn)等场景中编写 RunJS 代码时,运行时会把当前上下文绑定的 Collection 实例注入为ctx.collection。它的典型来源是ctx.blockModel.collection(父区块绑定的数据表)或ctx.collectionField?.collection(当前字段所属的数据表)。
从源码结构看,RunJS 上下文的定义位于 flow-engine 包的 runjs-context 目录中,例如 JSFieldRunJSContext.ts 与 JSColumnRunJSContext.ts 都显式声明了collection属性,并注明其为"集合定义元数据(只读,描述字段所属集合的 Schema)"。也就是说,ctx.collection是一份只读的元数据视图——适合读取结构信息,而不是用来直接执行数据库增删改(那属于ctx.resource/ API 的范畴)。
// 类型定义 collection: Collection | null | undefined;需要特别注意空值:在数据区块、表单区块、表格区块等"绑定数据表"的场景下它通常可用;但独立 JSBlock 若未绑定数据表,ctx.collection可能为null或undefined,使用前建议做空值判断(下文示例均采用?.访问)。
二、适用场景速查
| 场景 | 说明 |
|---|---|
| JSBlock | 区块绑定的数据表,可访问name、getFields、filterTargetKey等 |
| JSField / JSItem / JSColumn | 当前字段所属数据表(或父区块数据表),用于获取字段列表、主键等 |
| 表格列 / 详情区块 | 根据数据表结构渲染、打开弹窗时传入filterByTk等 |
一个容易踩坑的点:在子表格、关联字段等场景中,ctx.collection可能指向的是关联目标数据表(子表),而不是父区块绑定的数据表,此时它与ctx.blockModel.collection并不相同。
三、常用属性详解
| 属性 | 类型 | 说明 |
|---|---|---|
name | string | 数据表名称(如users、orders),常用于动态拼接 API 资源名 |
title | string | 数据表标题(含国际化) |
filterTargetKey | string \| string[] | 主键字段名,用于filterByTk、getFilterByTK |
dataSourceKey | string | 数据源 key(如main),可据此判断数据表属于主数据源还是外部数据源 |
dataSource | DataSource | 所属数据源实例 |
template | string | 数据表模板(如general、file、tree),可用于区分普通表、文件表、树形表 |
titleableFields | CollectionField[] | 可作为标题展示的字段列表 |
titleCollectionField | CollectionField | 标题字段实例 |
这些属性在动态场景下尤其有用。例如借助name可以写出"与表名解耦"的代码:
// 用数据表名称动态请求资源,避免硬编码 const res = await ctx.resource[ctx.collection.name].get({ filterByTk: ctx.record?.[ctx.collection.filterTargetKey], });借助template可以针对不同形态的表采取不同渲染策略(如树形表与普通表);借助titleableFields/titleCollectionField可以动态决定"记录标题"用哪个字段展示。
四、常用方法详解
| 方法 | 说明 |
|---|---|
getFields(): CollectionField[] | 获取全部字段(含继承),用于遍历字段做校验、联动、渲染 |
getField(name: string): CollectionField \| undefined | 按字段名获取单个字段 |
getFieldByPath(path: string): CollectionField \| undefined | 按路径获取字段(支持关联,如user.name) |
getAssociationFields(types?): CollectionField[] | 获取关联字段,types可为['one']、['many']等 |
getFilterByTK(record): any | 从记录中提取主键值,用于 API 的filterByTk |
几点关键行为:
getFields()的继承合并规则:它会合并继承数据表的字段,且自身字段覆盖同名的继承字段,因此拿到的字段列表就是当前数据表"最终生效"的完整结构。getFilterByTK(record)是filterTargetKey的配套方法,等价于"从一条记录中取出主键值",在需要批量构造filterByTk参数时比手动取字段更稳。getFieldByPath支持关联路径(如user.name),适合需要跨表读取字段定义的联动逻辑。
五、与 ctx.collectionField、ctx.blockModel 的关系
三者经常配合使用,按"想要什么"选择正确的入口:
| 需求 | 推荐用法 |
|---|---|
| 当前上下文关联的数据表 | ctx.collection(等价于ctx.blockModel?.collection或ctx.collectionField?.collection) |
| 当前字段的数据表定义 | ctx.collectionField?.collection(字段所属数据表) |
| 关联目标数据表 | ctx.collectionField?.targetCollection(关联字段的目标数据表) |
也就是说:在普通表单 / 表格中,ctx.collection通常就是区块绑定的数据表;而在子表格等嵌套场景中,它更可能"下沉"为当前字段所在的那张表。当需要区分"父表"和"当前表"时,优先显式使用ctx.blockModel.collection与ctx.collectionField?.targetCollection,语义更清晰。
相关文档可继续阅读 ctx.collectionField、ctx.blockModel、ctx.model。
六、实战示例
6.1 获取主键并打开弹窗
打开记录详情 / 编辑弹窗时,filterByTk参数需要的是主键值。用filterTargetKey取主键字段名(回退'id'),可以兼容自定义主键的表。仓库中表格单元格打开弹窗的场景片段(cell-open-dialog.snippet.ts)也是围绕这一模式组织的:
const primaryKey = ctx.collection?.filterTargetKey ?? 'id'; await ctx.openView(popupUid, { mode: 'dialog', params: { filterByTk: ctx.record?.[primaryKey], record: ctx.record, }, });6.2 遍历字段做必填校验或联动
利用getFields()拿到完整字段列表,再结合ctx.form逐字段取当前值,即可实现"不写死字段名"的动态必填校验:
const fields = ctx.collection?.getFields() ?? []; const requiredFields = fields.filter((f) => f.options?.required); for (const f of requiredFields) { const v = ctx.form?.getFieldValue(f.name); if (v == null || v === '') { ctx.message.warning(`${f.title} 为必填`); return; } }同样的遍历模式也可以用来做字段联动(例如根据f.options中的配置判断某字段是否应禁用)。
6.3 获取关联字段构建子表格
getAssociationFields(types)支持按关联类型过滤,传入['many']可只拿到一对多关联字段,用于自动发现"这张表有哪些子表可挂载":
const oneToMany = ctx.collection?.getAssociationFields(['many']) ?? []; // 用于构建子表格、关联资源等七、注意事项
filterTargetKey是数据表的主键字段名;部分数据表可能为string[]复合主键;未配置时常用'id'作为回退。- 在子表格、关联字段等场景,
ctx.collection可能指向关联目标数据表,与ctx.blockModel.collection不同,注意区分"当前表"与"父表"。 getFields()会合并继承数据表的字段,自身字段覆盖同名继承字段。ctx.collection为只读的 Schema 元数据视图,且独立 JSBlock 未绑表时可能为null,建议统一使用?.与回退值访问。
八、小结
ctx.collection是 NocoBase RunJS 中"让代码认识数据表结构"的入口:name/filterTargetKey/template等属性解决"这是哪张表、主键是谁",getFields/getFieldByPath/getAssociationFields等方法解决"它有哪些字段、如何遍历联动"。配合ctx.form、ctx.resource、ctx.openView等上下文能力,即可写出与具体表名解耦、可复用于多张数据表的通用 JS 逻辑。其定义可追溯至 JSFieldRunJSContext.ts 等 runjs-context 上下文实现,相关行为亦有 flow-engine 包的 RunJS 上下文测试(如 runjsContext.test.ts)覆盖。
【免费下载链接】nocobaseNocoBase is an open-source AI + no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考