React 360 原生模块实战:基于 NativeModules 示例理解运行时与 React 之间的双向通信
前端3D渲染【免费下载链接】react-360Create amazing 360 and VR content using React项目地址https://gitcode.com/gh_mirrors/re/react-360点击查看免费下载React 360 将应用划分为「主浏览器窗口运行时」与「Web Worker 中的 React 代码」两个隔离环境而Native Modules原生模块正是二者之间传递数据与调用的桥梁。本文以仓库中的 NativeModules 示例 为主线结合BrowserInfoModule的完整实现与底层桥接源码带你掌握常量暴露、无反馈方法、回调Callback与 Promise 四种通信模式并在自己的项目中注册与调用自定义原生模块。示例概览运行前先读懂它示例代码位于仓库的 Samples/NativeModules 目录其作用是在一个 2D 面板中演示「主窗口 → React」的全部四种数据传递方式常量展示浏览器userAgent字符串无反馈方法修改浏览器窗口标题回调方法异步获取电池电量并回传Promise 方法弹出确认对话框并根据用户选择 resolve / reject。要亲自运行它可以先用 React 360 CLI 创建新项目然后把Samples/NativeModules目录下的文件复制到项目目录中。启动开发服务器后访问http://localhost:8081/index.html即可在 360 全景背景360_world.jpg前看到一个包含按钮和信息的交互面板。整个示例由三个文件构成各自职责清晰文件职责BrowserInfoModule.js自定义原生模块运行在主窗口侧client.js运行时入口负责注册模块、挂载 React 根组件index.jsReact 侧组件通过NativeModules.BrowserInfo消费模块能力通信模型为什么需要回调而非返回值在深入代码之前需要先理解 React 360 的通信架构。React 代码运行在Web Worker中与主线程的 UI 渲染和浏览器 API 访问相互隔离参见 ReactNativeContext.js 顶部注释React bundle 异步运行在 Web Worker 中以不打断浏览器主线程。因此主窗口可以单向访问 React把方法调用、事件推送到 WorkerReact 无法同步等待主窗口的返回值——Native Module 的方法一旦被调用返回值不会直接回到调用方异步数据只能通过回调函数或Promise传回 React 侧。这一模型决定了原生模块的四种暴露形态也对应 Module.js 中__describe()的反射逻辑模块在注册时被描述为「常量 函数列表 Promise 函数列表 同步函数列表」的协议交由桥接层在初始化时与 Worker 建立契约。第一步编写原生模块 BrowserInfoModule示例的自定义模块定义在 BrowserInfoModule.js 中它继承自react-360-web导出的基类Moduleimport {Module} from react-360-web; export default class BrowserInfoModule extends Module { constructor(ctx) { super(BrowserInfo); this._rnctx ctx; this.userAgent navigator.userAgent; } // ...以下方法 }构造函数做了两件关键的事super(BrowserInfo)声明模块名称注册后 React 侧可通过NativeModules.BrowserInfo访问this.userAgent navigator.userAgent把当前浏览器的 UA 字符串挂到实例属性上。暴露常量构造时即定React 侧只读userAgent是常量的典型用例——它属于初始化时就已确定、之后不再变化的信息类似浏览器的语言或 UA。当模块注册时这个值会被直接复制给 React 侧之后对它的任何修改都不会同步到 React因为常量契约只在握手时建立一次。下划线规则贯穿所有属性任何以_开头的属性都不会被暴露如this._rnctx只是模块内部持有的运行时上下文引用。这条规则在 Module.js 的__describe()中有明确实现——反射阶段会跳过_前缀的属性与constructor。React 侧读取常量的方式非常直接index.jsText style{styles.userAgentString}{BrowserInfo.userAgent}/Text无反馈方法单向调用执行副作用setTitle()是调用即结束、无需回传的方法示例它在主窗口侧直接执行浏览器副作用setTitle(title) { document.title title; }这类方法在 Web Worker 中无法执行Worker 没有document访问权因此必须放在原生模块中。React 侧通过按钮触发incrementTitle() { this.titleCount; BrowserInfo.setTitle(Count: this.titleCount); }注意与常量同理如果你希望某个方法不被React 侧访问例如仅模块内部使用的辅助函数只需在方法名前面加上_前缀即可。回调方法异步数据回传的标准姿势获取电池电量属于需要把数据从主窗口传回 React的场景。由于无法返回值示例采用标准的异步回调结构BrowserInfoModule.jsgetBatteryLevel(cb) { const getBattery navigator.mozGetBattery || navigator.getBattery; getBattery .call(navigator) .then( battery battery.level, // 成功提取电量 () null // 失败返回 null ) .then(level { if (this._rnctx) { // 触发 React 侧的回调 this._rnctx.invokeCallback(cb, [level]); } }); }关键点在于最后一行的invokeCallback第一个参数是回调标识符cb由 React 侧在调用方法时传入第二个参数是传给回调的参数数组你可以在数组中放入任意数量的参数。模块在构造时保存的_rnctx即ReactNativeContext提供了这个桥接入口其实现位于 ReactNativeContext.jsinvokeCallback通过 executor 将回调调用序列化后投递给 Worker 中的 React 代码。React 侧的使用方式index.jscomponentDidMount() { // 组件初始化时请求电量回调在主窗口侧数据就绪后被触发 BrowserInfo.getBatteryLevel(level { this.setState({batteryLevel: level}); }); }渲染时把小数电量换算成百分比展示{this.state.batteryLevel null ? Unknown : ((batteryLevel * 100) | 0) %}Promise 方法$前缀自动生成 resolve / reject回调虽然直接但现代 JavaScript 更偏好用 Promise 组织可读的异步逻辑。Native Modules 提供了简洁的约定给方法名加上$前缀React 侧调用时就会自动得到 Promise。示例中的$getConfirmation()BrowserInfoModule.js$getConfirmation(message, resolve, reject) { const result window.confirm(message); if (this._rnctx) { if (result) { this._rnctx.invokeCallback(resolve, []); } else { // reject 时需提供 message用于在 React 侧填充 Error 对象 this._rnctx.invokeCallback(reject, [{message: Canceled the dialog}]); } } }需要注意的方法签名变化$前缀方法在运行时侧会自动多出两个回调参数——resolve和reject分别对应 React 侧 Promise 的两个终态。方法名中的$在 React 侧会被剥掉因此调用名是getConfirmation()而非$getConfirmation()。这一约定同样在 Module.js 中实现反射阶段识别$前缀后将方法归入promiseFunctions列表并在注册名中移除$。React 侧使用index.jsgetConfirmation() { BrowserInfo.getConfirmation(Resolve the Promise?).then( () { this.setState({lastConfirmation: true}); // Resolved }, () { this.setState({lastConfirmation: false}); // Rejected }, ); }第二步在运行时注册模块模块只有在初始化时注册React 侧才能使用。示例的 client.js 演示了函数式初始化的注册方式import {ReactInstance} from react-360-web; import BrowserInfoModule from ./BrowserInfoModule; function init(bundle, parent, options {}) { const r360 new ReactInstance(bundle, parent, { fullScreen: true, nativeModules: [ ctx new BrowserInfoModule(ctx), // 用函数形式拿到 ctx 引用 ], ...options, }); r360.renderToSurface( r360.createRoot(NativeModulesSample, {}), r360.getDefaultSurface(), ); r360.compositor.setBackground(./static_assets/360_world.jpg); } window.React360 {init};nativeModules数组支持两种元素模块实例new MyModule()初始化函数ctx new MyModule(ctx)——当模块需要在构造时拿到ReactNativeContext用于后续invokeCallback时必须使用这种形式。在 ReactInstance.js 中可以看到框架会把内置模块EnvironmentModule、AudioModule、VideoModule与你在options.nativeModules中传入的自定义模块合并注册到 Runtime随后由 Runtime 侧完成registerModule与握手描述见 ReactNativeContext.js 的内置模块注册列表以及describe()函数对每个模块执行__describe()生成通信协议。第三步React 侧的消费入口React 侧通过NativeModules命名空间取回模块index.jsimport {NativeModules} from react-360; const BrowserInfo NativeModules.BrowserInfo;随后即可像使用普通对象一样调用常量与方法。示例面板用VrButton封装了带悬停反馈的按钮并维护lastConfirmation状态来展示 Promise 的终态Resolved / Rejected / None完整样式与交互逻辑都可在 index.js 中查看。页面入口 index.html 则负责加载运行时 bundle 并触发React360.init(index.bundle?platformvrdevtrue, ...)。底层机制速览一次握手定协议将整个示例串起来的底层机制是运行时与 Worker 在启动时的一次握手所有注册的模块内置 自定义逐个调用Module.__describe()Module.js反射出常量除_前缀外的实例属性普通方法原型上的函数按序编号加入函数映射表Promise 方法$前缀函数自动附加两个回调 ID同步函数框架预留、当前未启用describe()汇总所有模块描述构成桥接协议ReactNativeContext.js握手完成后React 侧按协议索引调用方法、投递回调运行时侧则通过invokeCallback把数据送回 Worker。理解这一点后再回头看示例中的四种模式会发现它们只是同一张协议表的不同列而已——这也是复用示例即可覆盖 Native Modules 全部用法的原因。关于各模式的完整语法与更多边界规则如常量断言、_私有化、错误处理可进一步查阅 Native Modules 完整文档想探索更多官方示例的运行方式参考 示例导航说明 与 示例索引。小结通过 NativeModules 示例你可以在一份可运行的代码中完整掌握 React 360 原生模块的四种通信形态形态关键词典型场景常量构造时挂载属性UA、语言、版本号等一次性信息无反馈方法普通函数修改标题、触发主窗口副作用回调方法invokeCallback(cb, [args])异步取数电量、控制器信息Promise 方法$前缀 resolve/reject需要明确成功/失败语义的异步操作核心原则只有一条React 与主窗口之间无法同步返回一切数据传递都走回调或 Promise而_与$两个前缀则是控制暴露范围与异步形态的开关。据此你就能把任何浏览器独有能力存储、设备信息、环境操作安全地桥接进自己的 React 360 应用。赞分享前端3D渲染【免费下载链接】react-360Create amazing 360 and VR content using React项目地址https://gitcode.com/gh_mirrors/re/react-360点击查看免费下载相关推荐Rivet Actors 原生 WebSocket 集成实战基于 Native WebSockets 示例实现实时双向通信Rivet Actors 原生 WebSocket 集成实战基于 Native WebSockets 示例实现实时双向通信 导读 本文围绕仓库中的 examp后端AI Agent人工智能流程编排WebSocketUnity3DTraining 之 AndroidJavaProxyUnity 与 Android 原生双向通信实战解析Unity3DTraining 之 AndroidJavaProxyUnity 与 Android 原生双向通信实战解析 导读 本文以 SDK 接入研究文档示例工程React 360 幻灯演示示例解析基于 Environment 模块构建 360/180 全景照片查看器React 360 幻灯演示示例解析基于 Environment 模块构建 360/180 全景照片查看器 本文以 React 360 官方仓库中的 360前端3D渲染上一篇一键重塑光影Qwen-Edit-2509 LoRA插件让图像照明编辑效率提升300%下一篇OneUptime监控事件自动化处理剧本编排与自动修复创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考