NocoBase RunJS ctx.on() 事件订阅指南:字段双向绑定、资源刷新监听与监听器生命周期管理
2026/9/17 14:54:21 网站建设 项目流程

NocoBase RunJS ctx.on() 事件订阅指南:字段双向绑定、资源刷新监听与监听器生命周期管理

【免费下载链接】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

RunJS 是 NocoBase 中用于JS 区块(JSBlock)JS 字段(JSField / JSEditableField)JS 操作(JSAction)等场景的 JavaScript 执行环境,代码运行在受限沙箱中并通过统一的ctx上下文访问页面能力(参见 RunJS 概述)。ctx.on()是其中负责订阅上下文事件的核心 API:通过它可以在字段值被外部修改时同步更新 UI、响应资源刷新/保存等生命周期事件,从而把自定义 JS 代码无缝接入表单联动、数据刷新等 NocoBase 既有机制。读完本文,你将掌握ctx.on()的类型定义、事件分发规则、与ctx.off()的配对清理模式,以及js-field:value-changeresource:refresh等事件的底层实现与完整实战写法。

ctx.on() 的定位与适用场景

在 RunJS 脚本中,ctx.on()用于订阅"上下文事件"——例如字段值变化、属性变化、资源刷新等。事件会根据名称前缀被映射到两种不同的通道:

  • resource:为前缀的事件 → 走ctx.resource的内部事件总线;
  • 其余事件 → 通常映射为ctx.element上的自定义 DOM 事件(CustomEvent)。

ctx.on()的典型适用场景如下:

场景说明
JSField / JSEditableField监听字段值从外部(表单、联动等)变更时同步更新 UI,实现双向绑定
JSBlock / JSItem / JSColumn监听容器上的自定义事件,响应数据或状态变化
resource 相关监听资源刷新、保存等生命周期事件,在数据更新后执行逻辑

类型定义

on(eventName: string, handler: (event?: any) => void): void;
  • eventName:事件名称字符串。以resource:开头的名称走资源事件总线,其余名称走ctx.element上的 DOM 事件(若容器存在)。
  • handler:事件回调。DOM 事件通道下,参数为事件对象,新值通过ev.detail携带。

常见事件一览

事件名说明事件来源
js-field:value-change字段值被外部修改(如表单联动、默认值更新)ctx.element上的 CustomEvent,ev.detail为新值
resource:refresh资源数据已刷新ctx.resource事件总线
resource:saved资源保存完成ctx.resource事件总线

事件最终映射规则:以resource:为前缀的走ctx.resource.on,其余通常走ctx.element上的 DOM 事件(若存在)。

实战示例

字段双向绑定(React useEffect + 清理)

在 JS 字段中渲染自定义 UI 时,通常需要把本地 React state 与表单字段值保持同步。以下代码在挂载时订阅js-field:value-change,并在卸载时通过 cleanup 移除监听:

React.useEffect(() => { const handler = (ev) => setValue(ev?.detail ?? ''); ctx.on?.('js-field:value-change', handler); return () => { ctx.off?.('js-field:value-change', handler); }; }, []);

这里ev.detail即外部写入的新字段值;回调中把它同步到本地setValue,实现"外部值变化 → UI 更新"的单向数据流。配合ctx.setValue/ctx.getValue(参见 ctx.setValue())即可构成完整的双向绑定。

原生 DOM 监听(ctx.on 不可用时的替代)

不同 context 类型提供的能力不同,ctx.on可能不存在。此时可直接使用ctx.element的原生addEventListener

// 当 ctx.on 未提供时,可直接使用 ctx.element const handler = (ev) => { if (selectEl) selectEl.value = String(ev?.detail ?? ''); }; ctx.element?.addEventListener('js-field:value-change', handler); // 清理时:ctx.element?.removeEventListener('js-field:value-change', handler);

资源刷新后更新 UI

对于依赖资源数据的场景,可以订阅ctx.resource的事件总线,在数据刷新后重新读取数据并渲染:

ctx.resource?.on('refresh', () => { const data = ctx.resource?.getData?.(); // 根据 data 更新渲染 });

从源码看,这一模式在 NocoBase 内置模型中被广泛使用。例如 JSItemActionModel.tsx 中即存在resource.on('refresh', handler)resource.off('refresh', handler)的配对订阅写法,可作为自定义脚本的参照实现。

源码级原理:js-field:value-change 是如何产生的

ctx.on之所以能监听到字段值变化,是因为渲染层在值变更时向容器派发了标准的 DOM CustomEvent。以可编辑 JS 字段为例,其实现位于 JSEditableFieldModel.tsx:

  1. 事件派发:在JSFormRuntime组件中,useEffect监听value变化并派发事件:
useEffect(() => { if (!containerRef.current || !scriptCode) return; const event = new CustomEvent('js-field:value-change', { detail: value }); containerRef.current.dispatchEvent(event); }, [value, scriptCode]);

这里detail携带的正是最新字段值,与文档中"ev.detail为新值"的约定完全对应(见 JSEditableFieldModel.tsx)。

  1. 容器与 element 暴露:脚本真正运行前,runJs步骤的 handler 通过ctx.onRefReady等待容器引用就绪,再以ctx.defineProperty('element', ...)暴露容器——且返回值包裹在ElementProxy中(带 XSS 保护),并通过动态 getter 绑定ref.current,避免容器变更后失效(见 JSEditableFieldModel.tsx)。

  2. 默认模板即使用该事件JSEditableFieldModel的默认脚本模板本身就演示了"监听js-field:value-change→ 同步本地 state →onChange时回写ctx.setValue"的完整闭环(见 JSEditableFieldModel.tsx):

React.useEffect(() => { const handler = (ev) => setValue(ev?.detail ?? ''); ctx.element?.addEventListener('js-field:value-change', handler); return () => ctx.element?.removeEventListener('js-field:value-change', handler); }, []);
  1. 测试验证:该行为有对应的单元测试覆盖,见 JSEditableFieldModel.test.tsx 中的EDITABLE_CODE用例——它直接以ctx.element?.addEventListener('js-field:value-change', handler)作为被测脚本,验证外部值变化能够驱动 UI 同步。

只读字段(JSFieldModel)中的事件语义

对于只读形态的 JS 字段(如表格列、详情项),模型实现位于 JSFieldModel.tsx:它渲染一个占位<span>容器,当props.value变化时通过useHooksBeforeRender重新执行jsSettings脚本刷新内容(见 JSFieldModel.tsx)。由于容器在只读场景同样存在,js-field:value-change的 DOM 监听模式依旧可用;而ctx.setValue这类写能力则仅在带表单绑定的上下文中提供。

与 ctx.off 的配合

ctx.on注册的监听必须成对管理,具体规则(参见 ctx.off()):

  • 使用ctx.on注册的监听,应在适当时机通过ctx.off移除,避免内存泄漏或重复触发;
  • 在 React 中,通常在useEffect的 cleanup 函数中调用ctx.off
  • ctx.off可能不存在,使用时建议加可选链:ctx.off?.('eventName', handler)

资源事件总线同样遵循这一模式:

const handler = () => { /* ... */ }; ctx.resource?.on('refresh', handler); // 适当时机 ctx.resource?.off('refresh', handler);

注意事项

  1. 配对取消:每次ctx.on(eventName, handler)都应有对应的ctx.off(eventName, handler),且传入的handler引用必须一致——不同的函数引用无法正确移除监听。
  2. 生命周期:在组件卸载或 context 销毁前移除监听,否则可能导致内存泄漏。从源码结构看,NocoBase 的字段模型在挂载时会做首次执行的幂等保护(如JSFieldModel中的_mountedOnce标记,见 JSFieldModel.tsx),但自定义脚本中的监听清理责任仍在脚本作者一方。
  3. 事件可用性:不同 context 类型支持的事件不同,具体以各组件文档为准;ctx.on缺失时请退回到ctx.element的原生addEventListener
  4. 可写能力按需判断js-field:value-change只负责"通知值变化",写入操作应通过ctx.setValue?.(value)完成,且仅在带表单绑定的上下文中可用(详见 ctx.setValue() 的注意事项)。

相关文档

  • ctx.off() - 移除事件监听
  • ctx.element - 渲染容器与 DOM 事件
  • ctx.resource - 资源实例及其on/off
  • ctx.setValue() - 设置字段值(会触发js-field:value-change

【免费下载链接】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),仅供参考

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

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

立即咨询