RNOH 调试调测不迷路:Metro、JavaScript、ConfigT一次讲清
Metro热加载
React Native 使用Metro构建 JavaScript 代码和资源,本节介绍了配置步骤和使用方式。
配置Metro
您需要在项目的 metro.config.js 文件中配置 harmony 平台的 Metro 配置选项。它可以导出:
- 一个对象(推荐),将与Metro的内部配置默认值合并。
- 一个函数,该函数将使用Metro的内部配置默认值被调用,并返回最终的配置对象。
在 React Native 中,你的 Metro 配置应该扩展https://www.npmjs.com/package/@react-native/metro-config@react-native/metro-config或https://www.npmjs.com/package/@expo/metro-config@expo/metro-config。这些包含构建和运行 React Native 应用所需的基本默认值。
为了在 harmony 平台上配置 Metro,您还需要拓展 react-native-harmony,通过 createHarmonyMetroConfig 创建 harmony 平台的 Metro。
下面是示例工程中默认的 metro.config.js 文件:
// metro.config.js
const { mergeConfig, getDefaultConfig } = require('@react-native/metro-config');
const {
createHarmonyMetroConfig,
} = require('@react-native-oh/react-native-harmony/metro.config');
/**
* Metro配置
* https://metrobundler.dev/docs/configuration
*
* @type {import("metro-config").ConfigT}
*/
const config = {
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
}),
},
};
module.exports = mergeConfig(
getDefaultConfig(__dirname),
createHarmonyMetroConfig({
reactNativeHarmonyPackageName: '@react-native-oh/react-native-harmony',
}),
config,
);
如果您使用的是 RNApp 启动的 RN 框架,那么这一步可以跳过。如果您使用的是 RNSurface 启动的 RN 框架,您就需要创建一个 RNComponentContext,并在 context 的 devToolsController 中增加如下的事件监听,并删除原有的 RNInstance 实例,重新启动一个新的 RNInstance 实例,即可启用热加载的功能:
this.ctx.devToolsController.eventEmitter.subscribe("RELOAD", async () => {
this.cleanUp();
this.init();
});
使用Metro
RNOH 提供了 MetroJSBundleProvider API 用于加载Metro服务提供的 jsBundle,使用方法如下:
场景一 使用
RNApp.ets如果你使用了 RNOH 提供的组件
RNApp,则只需要将new MetroJSBundleProvider()传给jsBundleProvider属性即可:RNApp({ ... // 方式1 // jsBundleProvider: new MetroJSBundleProvider() jsBundleProvider: new TraceJSBundleProviderDecorator( new AnyJSBundleProvider([ // 方式2 new MetroJSBundleProvider(), ]), this.rnohCoreContext.logger), })harmony 工程准备好后,用数据线将真机与电脑连接,打开新的命令行工具并执行:
hdc rport tcp:8081 tcp:8081接下来打开js侧控制台,在 RN 工程目录中执行以下命令启动 RN 应用:
npm run start执行成功后显示以下的内容:

start命令会使用工程目录下默认的metro.config.js,您也可以在package.json文件中配置start命令,使用自定义的 config 文件:··· "scripts": { "start": "react-native start --config metro.config.harmony.js" } ···场景二 不使用
RNApp.ets不使用
RNApp的话,需要开发者自己去创建并管理RNInstance,假设你已经拥有了一个RNInstance的实例rnInstance,加载 Metro 服务的方法可参考如下代码:rnInstance.runJSBundle(new MetroJSBundleProvider());harmony 工程准备好后,接下来打开 js 侧控制台,在 RN 工程目录中执行以下命令启动 RN 应用:
npm run start然后用数据线将真机与电脑连接,打开新的命令行工具并执行:
hdc rport tcp:8081 tcp:8081执行成功后显示以下的内容:

start命令会使用工程目录下默认的metro.config.js,您也可以在package.json文件中配置start命令,使用自定义的 config 文件:··· "scripts": { "start": "react-native start --config metro.config.harmony.js" } ···场景三 访问局域网中的 Metro 服务
方法一:
上面的方案需要连接数据线并转发8081端口,**RNOH** 另外还提供了另外一个 API(`MetroJSBundleProvider.fromServerIp`),该方法接收以下参数:ip- Metro 服务器的 IP 地址port- Metro 服务的端口地址,默认 8081appKeys- App name合集,默认空数组scheme- 协议,取值为'http'或'https',默认'http'tlsOptions- 可选。仅在scheme为'https'时生效,用于配置 CA 证书路径与证书校验策略(详见场景四)使用方法便是将
MetroJSBundleProvider.fromServerIp替换掉上面的new MetroJSBundleProvider(),如:
RNApp({ ... jsBundleProvider: new TraceJSBundleProviderDecorator( new AnyJSBundleProvider([ MetroJSBundleProvider.fromServerIp('192.168.43.14', 8081), ]), this.rnohCoreContext.logger), })方法二:
可以通过摇一摇弹出
React Native Dev Menu对话框,点击Settings选项,进入Settings页面后点击Debug Server host & port for device选项,会弹出输入框,输入地址后重新打开应用即可。输入支持IP:Port(默认按http处理)或带协议的完整地址(如http://10.0.0.1:8081、https://10.0.0.1:8081)。若使用https://,还需配置证书信任策略,详见场景四。
如果希望通过摇一摇打开
React Native Dev Menu对话框,需要在module.json5内配置权限"requestPermissions": [ { "name": "ohos.permission.ACCELEROMETER" } ],
这种方式只需要保证手机能连通电脑,打开 APP 后便能访问 Metro 服务,不需要连接数据线和转发端口。
场景四 Metro 支持 TLS(HTTPS / WSS)服务
RNOH 支持通过 HTTPS 从 Metro 拉取 JS Bundle;热更新(HMR)、Dev Menu 消息通道与调试器连接会随 Bundle URL 的协议自动升级为 WSS。适用于需要加密开发链路,或局域网内使用自签名 / 私有 CA 证书的场景。
1. 启动带 TLS 的 Metro 服务
先准备证书与私钥(可用
openssl、mkcert 等工具生成;若设备需通过局域网 IP 访问,证书 SAN 中需包含该 IP 或对应域名),然后在 RN 工程目录启动 Metro:npx react-native start --https --key ./certs/key.pem --cert ./certs/cert.pem也可在
package.json的start脚本中写入上述参数。--https开启 HTTPS;--key/--cert分别指定私钥与证书路径。2. 应用侧通过 HTTPS 加载 Bundle
使用
MetroJSBundleProvider.fromServerIp,将scheme设为'https',并按需传入tlsOptions:// caPath 为应用可读的 CA 证书绝对路径,例如将 rawfile 中的证书拷贝到 cacheDir 后的路径 const metroTlsOptions = { caPathProvider: (url: string) => url.includes('192.168.') ? caPath : '', remoteValidation: 'system', // 开发自测可临时改为 'skip'(跳过证书校验,仅用于开发测试) }; RNApp({ ... jsBundleProvider: new TraceJSBundleProviderDecorator( new AnyJSBundleProvider([ MetroJSBundleProvider.fromServerIp( '192.168.43.14', 8081, [], 'https', metroTlsOptions, ), ]), this.rnohCoreContext.logger), // 热更新 / 调试器等通道使用 RNInstance 上的 TLS 配置,需与 Bundle 拉取策略保持一致 rnInstanceConfig: { caPathProvider: metroTlsOptions.caPathProvider, remoteValidation: metroTlsOptions.remoteValidation, ... }, })tlsOptions/rnInstanceConfig中与 TLS 相关的字段说明:字段 说明 caPathProvider(url: string) => string。按 URL 返回 CA 证书路径;返回空字符串表示使用系统默认 CA。mkcert / 私有 CA 场景应返回应用可读的证书绝对路径remoteValidation证书校验模式: 'system'(系统 CA 校验)、'skip'(跳过校验,仅开发测试)、或ValidationCallback(自定义回调,API >= 26)注意:
- Bundle 拉取使用
MetroJSBundleProvider的tlsOptions;HMR、Inspector 等通道使用rnInstanceConfig中的caPathProvider/remoteValidation。两者建议配置一致。 - 若通过 Settings 配置
https://IP:Port并由new MetroJSBundleProvider()读取该地址,仍需将tlsOptions传入构造函数(例如new MetroJSBundleProvider('', [], metroTlsOptions)),并同步配置rnInstanceConfig。 - Worker 线程中的网络请求(如图片加载)需在
RNOHWorker.ets中单独配置remoteValidation/caPathProvider,详见如何配置自定义 HTTPS 证书验证。 remoteValidation: 'skip'仅用于开发测试,请勿用于生产环境。
- Bundle 拉取使用
使用 Metro 服务加载 bundle 时如下场景皆可触发应用刷新:
- 在编辑器中编辑代码,并保存,便可以看到改动点自动更新到手机上
- 在命令行工具中输入r,也会触发应用更新
- 在命令行工具中输入d,在弹出的
React Native Dev Menu对话框中选择 Reload 也会触发应⽤刷新
JS调试
打开React Native Dev Menu
注意:在 Release 模式下,Dev Menu 不可使用。
分别执行以下命令:
hdc rport tcp:8081 tcp:8081npm run start执行成功后显示以下的内容:

在手机上打开应用。
在命令行工具中输入
d,便可以看到手机上弹出了React Native Dev Menu对话框。
Element Inspector
您可以通过 Element Inspector 查看 RN 元素的盒子模型及样式,具体操作步骤如下:
通过 Metro 加载 bundle,打开 React Native Dev Menu
对话框后选择Toggle Element Inspector`。选择
Inspect,然后点击要审查的元素,便可看到黑色蒙层中显示出了元素的层级关系、样式以及盒子模型,如图:
点击蒙层中面包屑的其他节点可查看当前元素的父级/子级元素的样式。
重新点击页面上的其他元素,便可切换到该元素。
取消选中蒙层中的
Inspect,便可关闭元素审查。
断点调试
在启用断点调试之前,需要您启动RN实例的时候开启 debugger。您可以在 RNApp 中以 rnInstanceConfig 的参数的形式传入:
// index.ets
RNApp({
rnInstanceConfig: {
enableDebugger: true,
···
},
···
})
在编码过程中,您可以使用React DevTools 来设置断点,并调试您的代码。
报错信息输出
LogBox日志
LogBox 是手机侧的故障提示框,当js侧发生错误时手机的应用界面上便会弹出错误告警,如图:

右侧部分即为 LogBox,展示了错误的说明、源码以及调用栈信息。如果您使用的是Metro服务加载bundle,点击设备上 LogBox 中的调用栈,便可在电脑端中使用编辑器(如VSCode)自动打开对应的文件以供您查看并定位问题。
如果您使用的是 RNApp 启动的 RN 框架,那么这一步可以跳过。如果您使用的是 RNSurface 启动的 RN 框架,您就需要创建一个 RNComponentContext 并创建 LogBox 的构造器,并在 context 的 devToolsController 中增加如下的事件监听,并进行对应的启动和关闭 LogBox 弹窗的操作:
this.logBoxDialogController = new CustomDialogController({
cornerRadius: 0,
customStyle: true,
alignment: DialogAlignment.TopStart,
backgroundColor: Color.Transparent,
builder: LogBoxDialog({
ctx: RNComponentContext,
rnInstance: this.rnInstance,
initialProps: this.initialProps,
buildCustomComponent: this.buildCustomComponent,
})
})
···
this.rnInstance.getTurboModule<LogBoxTurboModule>(LogBoxTurboModule.NAME).eventEmitter.subscribe("SHOW", () => {
this.logBoxDialogController.open();
})
this.rnInstance.getTurboModule<LogBoxTurboModule>(LogBoxTurboModule.NAME).eventEmitter.subscribe("HIDE", () => {
this.logBoxDialogController.close();
})
控制台日志
DevEco Studio
当js侧发生错误时,通过以下方法可在 DevEco Studio 上查看到错误信息:
手机连接到电脑,打开
DevEco Studio,打开一个项目;在
DevEco Studio下方点击Log;在
HiLog的筛选栏中一次选择你连接的设备、All logs of selected app、[应用包名]、Warn/Error;设置完后,js 侧发生故障时,故障信息便能实时的显示在
DevEco Studio,如图:
命令行工具
若是通过Metro服务加载bundle,当js侧发生错误时,命令行工具便会输出错误发生的原因和具体位置,如图:

其他
在 React Native 中亦可通过 console.log、console.warn、throw 等触发一个日志或告警。
添加Trace
JS侧
可通过引入 react-native 提供的 Systrace 添加 Trace。
例如添加一个名为 ONPRESS 的同步 Trace,以跟踪一个按钮的 onPress:
import {Systrace} from 'react-native';
...
<Button
onPress={() => {
Systrace.beginEvent('ONPRESS');
// do something
Systrace.endEvent();
}}
/>
例如添加一个名为 ASYNC 的异步 Trace,通过两个按钮控制开始和结束:
import {Systrace} from 'react-native';
...
let traceCookie = null;
...
<Button
onPress={() => {
if (traceCookie === null) {
traceCookie =
Systrace.beginAsyncEvent('ASYNC');
}
}}
/>
<Button
onPress={() => {
if (traceCookie !== null) {
Systrace.endAsyncEvent('ASYNC', traceCookie);
}
}}
/>
/>
ArkTS侧
ArkTS 侧大多数类和对象的成员中存在一个 logger 或者 ctx 对象(ctx 对象内涵一个 logger,logger 内提供了封装的 startTracing() 用于添加 Trace)。
例如添加一个名为 myTrace 的 Trace:
const stopTracing = this.logger.clone("myTrace").startTracing();
// do something
stopTracing();
也可以直接使用未封装的 @ohos.hiTraceMeter 能力添加 Trace。
例如添加一个名为 myTrace 的 Trace,使用 0 作为识别标志(用于区分同名 Trace):
import hiTrace from '@ohos.hiTraceMeter';
...
hiTrace.startTrace(`myTrace`, 0)
// do something
hiTrace.finishTrace(`myTrace`, 0)
C++侧
可通过引入 react/renderer/debug/SystraceSection.h ,实例化一个 facebook::react::SystraceSection 对象添加 Trace。
Trace的范围与该 facebook::react::SystraceSection 对象的生命周期(作用域)相同,可以使用大括号控制范围。
例如添加一个名为 myTrace 的 Trace:
#include <react/renderer/debug/SystraceSection.h>
...
{
facebook::react::SystraceSection s("myTrace");
// do something
}
通过监听 RNOHErrors 捕获 Bundle 内错误信息
需要注意的是:该方法仅在打包 bundle 时关闭 dev 选项,即进行 product 模式打包时,才能对 bundle 内错误进行监听:
"npm run codegen && react-native bundle-harmony --dev=false"
可通过 RNOHCoreContext 获取上下文对象,使用 subscribeToRNOHError 进行监听。
如以下例子所示,可在 EntryAbility.ets 的 onCreate 对 bundle 内的错误进行监听。
import { RNAbility, RNInstanceError, RNOHCoreContext } from '@rnoh/react-native-openharmony';
import Want from '@ohos.app.ability.Want';
import {JSON} from '@kit.ArkTS';
export default class EntryAbility extends RNAbility {
getPagePath() {
return 'pages/Index';
}
override onCreate(want: Want): void {
super.onCreate(want);
AppStorage.get<RNOHCoreContext>('RNOHCoreContext')!.subscribeToRNOHErrors((err) => {
console.log('HERE: error logged from entryability! ' + JSON.stringify(err))
console.log('HERE: err.getMessage: ' + err.getMessage())
console.log('HERE: err.getSuggestions:' + JSON.stringify(err.getSuggestions()), null, 2)
console.log('HERE: err.getExtraData: ' + JSON.stringify(err.getExtraData()), null, 2)
console.log('HERE: err.getStack: ' + JSON.stringify(err.getStack(), null, 2))
//Not all errors are RNInstanceError's, so we need to check for that.
if (err instanceof RNInstanceError) {
console.log('HERE: err.getRNInstanceId: ' + err.getRNInstanceId())
console.log('HERE: err.getRNInstanceName: ' + err.getRNInstanceName())
}
})
}
}
在本例子中,主要是对捕获的 error 进行打印,因此在其中添加了 console.log(),在终端中打印出具体的 error 信息:
例如,当需要 error 中的 Message 信息时,直接调用 err.getMessage() 即可。
console.log('HERE: err.getMessage: ' + err.getMessage())
同时,也支持对 error 错误中的 RN 实例信息的补充,例如:
if (err instanceof RNInstanceError)
{
console.log('HERE: err.getRNInstanceId: ' + err.getRNInstanceId())
console.log('HERE: err.getRNInstanceName: ' + err.getRNInstanceName())
}
当捕获的 error 属于 RN 实例的 error 时,就打印出 RN 实例错误相关的信息。
更多推荐

所有评论(0)