NocoBase RunJS 深度指南:ctx.blockModel 让 JSField / JSItem / JSColumn 访问父区块的 form、collection 与 resource
【免费下载链接】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
本篇基于 NocoBase 官方 RunJS 上下文文档 block-model.md 展开,讲清ctx.blockModel的定位——当前 JS 字段 / JS 区块所在的父区块模型(BlockModel实例),以及它在 JSField、JSItem、JSColumn 场景中访问父表单/表格区块的form、collection、resource的完整用法。读完你能够:在字段级联动、表格行操作、表单校验与刷新等场景下正确取用父区块能力,理解ctx.blockModel与ctx.model、ctx.form、ctx.resource的分工,并从源码层面确认它的注入时机与空值边界。
一、ctx.blockModel 是什么
ctx.blockModel指向承载当前 JS 逻辑的父区块,即 NocoBase 客户端区块模型(BlockModel)的运行时实例。具体含义随 JS 载体而变:
- 在JSField、JSItem、JSColumn中,它指向承载当前 JS 逻辑的表单区块或表格区块;
- 在JSBlock 独立区块中,它可能为
null,或与ctx.model相同(即区块自身)。
它的核心定位是"从字段/列/子项的视角向上看一层":JS 逻辑本身通常挂在字段或列上,而数据刷新、选中行、表单实例等能力都定义在区块上,ctx.blockModel就是这条访问链。
官方类型定义为:
blockModel: BlockModel | FormBlockModel | TableBlockModel | CollectionBlockModel | DataBlockModel | null;具体类型取决于父区块类型:表单区块多为FormBlockModel、EditFormModel,表格区块多为TableBlockModel。
二、ctx.blockModel 如何被注入(源码依据)
从源码结构看,ctx.blockModel并非静态属性,而是在区块模型初始化时动态挂载到执行上下文上的。NocoBase 客户端 v2 的区块基类 BlockModel.tsx 在onInit中完成了这一注入:
onInit(options: any): void { super.onInit(options); this.context.defineProperty('blockModel', { value: this, }); // ... }也就是说,只要某个区块(BlockModel 子类)初始化了自身的 flow context,其context.blockModel就会被固定为该区块实例。CollectionBlockModel.tsx 对数据类区块做了同样的挂载,并在数据加载逻辑中大量使用ctx.model上的resource与数据加载模式(getDataLoadingMode()、hasActiveFilters()),这解释了为什么文档中resource.refresh()等能力"仅在数据区块下存在"。
另一处关键证据在 flow-engine 侧:flowContext.ts 中通过context?.blockModel === m判断一个模型是否为"自身即区块"的实例,并在多处(如 L1063 附近)以getMaybe(() => (evalCtx as any).blockModel)安全读取该属性。这印证了文档的两条注意事项:
- 独立 JSBlock 无父区块时,
ctx.blockModel可能为null,使用前应做空值判断; - 在 JSBlock 中它"可能为自身或上层区块,取决于实际层级"——源码用
context?.blockModel === m这种比较正是为了区分"自身即父区块"的情况。
此外,BlockModel基类还定义了区块场景枚举 BlockSceneEnum(new/one/many/select/filter/subForm/bulkEditForm),从源码结构看,父区块处于哪种场景会影响 JSField 内可用的字段与联动语义,这也是"具体类型取决于父区块类型"背后的实现背景。
三、适用场景
| 场景 | 说明 |
|---|---|
| JSField | 表单字段内访问父表单区块的form、collection、resource,实现联动或校验 |
| JSItem | 子表格项中访问父表格/表单区块的资源、数据表信息 |
| JSColumn | 表格列中访问父表格区块的resource(如getSelectedRows)、collection |
| 表单操作 / 事件流 | 访问form做提交前校验、resource做刷新等 |
注意:
ctx.blockModel仅在存在父区块的 RunJS 上下文中可用;独立 JSBlock(无父表单/表格)时可能为null,使用前建议做空值判断。
四、常用属性
| 属性 | 类型 | 说明 |
|---|---|---|
uid | string | 区块模型唯一标识 |
collection | Collection | 当前区块绑定的数据表 |
resource | Resource | 区块使用的资源实例(SingleRecordResource/MultiRecordResource等) |
form | FormInstance | 表单区块:Ant Design Form 实例,支持getFieldsValue、validateFields、setFieldsValue等 |
emitter | EventEmitter | 事件发射器,可监听formValuesChange、onFieldReset等 |
几点使用边界(与官方文档"注意事项"一致):
resource仅在数据区块下存在——源码中数据加载、刷新逻辑确实都挂在CollectionBlockModel及其resource上(见 CollectionBlockModel.tsx 中对blockModel.resource、getDataLoadingMode()的调用);form仅在表单区块下存在,表格区块通常无form;- 属性不存在时用可选链访问是最安全的写法,例如
ctx.blockModel?.resource?.refresh?.()。
五、与 ctx.model、ctx.form 的关系
三个入口各司其职,官方推荐用法如下:
| 需求 | 推荐用法 |
|---|---|
| 当前 JS 所在的父区块 | ctx.blockModel |
| 读写表单字段 | ctx.form(等价于ctx.blockModel?.form,表单区块下更便捷) |
| 当前执行上下文所在模型 | ctx.model(JSField 中为字段模型,JSBlock 中为区块模型) |
在 JSField 中,ctx.model为字段模型,ctx.blockModel为承载该字段的表单/表格区块;ctx.form通常即ctx.blockModel.form。换言之:
- 需要"我这条 JS 属于哪个区块"→ 用
ctx.blockModel; - 需要"我当前操作的对象是字段还是区块"→ 用
ctx.model; - 需要读写字段值 → 表单区块下直接用
ctx.form更便捷,其余场景走ctx.blockModel?.form。
ctx.resource同理,等价于ctx.blockModel?.resource,有则直接使用(参见 ctx.resource 文档)。
六、实战示例
以下示例完整继承自官方文档,可直接用于 JSField / JSColumn / JSAction 的脚本中。
表格:获取选中行并处理
在表格区块的 JSColumn 或行操作场景中,通过父表格区块的resource取当前选中的行:
const rows = ctx.blockModel?.resource?.getSelectedRows?.() || []; if (rows.length === 0) { ctx.message.warning('请先选择数据'); return; }表单场景:校验并刷新
提交前校验整个表单,成功后刷新区块数据。validateFields()来自父表单区块的 Ant Design Form 实例,resource.refresh()来自数据区块的资源实例:
if (ctx.blockModel?.form) { await ctx.blockModel.form.validateFields(); await ctx.blockModel.resource?.refresh?.(); }监听表单变化
通过区块模型上的事件发射器监听表单值变化,做联动或重新渲染。这类用法在 NocoBase 内部也被广泛使用——例如 dataScopeFormValueClear.test.ts 等测试用例就以blockModel: formBlock构造上下文,验证表单值变化驱动数据范围清理等联动行为;flow-engine 侧的 flowContext.test.ts 也定义了BlockModelLike等桩模型来验证ctx.blockModel的上下文解析:
ctx.blockModel?.emitter?.on?.('formValuesChange', (payload) => { // 根据最新表单值做联动或重新渲染 });触发区块重新渲染
ctx.blockModel?.rerender?.();七、注意事项(完整清单)
- 空值防御:
ctx.blockModel在独立 JSBlock(无父表单/表格区块)时可能为null,访问其属性前建议使用可选链:ctx.blockModel?.resource?.refresh?.()。 - 载体差异:在JSField / JSItem / JSColumn中,
ctx.blockModel为承载当前字段的表单或表格区块;在JSBlock中,可能为自身或上层区块,取决于实际层级(源码以context?.blockModel === m判断"自身即父区块",见 flowContext.ts)。 - 属性存在性:
resource仅在数据区块下存在;form仅在表单区块下存在,表格区块通常无form。
八、源码与测试索引
| 内容 | 路径 |
|---|---|
区块基类与ctx.blockModel注入(onInit中defineProperty('blockModel')) | BlockModel.tsx |
数据区块模型:resource与数据加载模式 | CollectionBlockModel.tsx |
区块场景枚举BlockSceneEnum | BlockModel.tsx |
flow-engine 对ctx.blockModel的读取与自身区块判定 | flowContext.ts |
ctx.blockModel相关行为测试(表单联动、数据范围清理) | dataScopeFormValueClear.test.ts |
| RunJS 表单提交链路测试 | runjsFormSubmit.test.ts |
| 本上下文官方文档 | block-model.md |
九、相关文档
- ctx.model:当前执行上下文所在模型
- ctx.form:表单实例,表单区块下常用
- ctx.resource:资源实例(等价于
ctx.blockModel?.resource,有则直接使用) - ctx.getModel():按 uid 获取其他区块模型
- RunJS 总览:runjs 文档目录
综合来看,ctx.blockModel是 NocoBase RunJS 体系中"字段级 JS 与区块级能力"之间的桥梁:字段脚本负责细粒度逻辑,区块模型负责数据与表单资源。理解BlockModel.onInit的注入机制、CollectionBlockModel的资源语义,以及"独立区块可能为 null"的边界,就能在联动、校验、刷新、选中行处理等实战场景中正确、安全地使用它。
【免费下载链接】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),仅供参考