React Native for OpenHarmony 实战:三方库 react-native-button 的鸿蒙化适配指南
本文记录把 react-native-button(经典按钮组件)适配到 HarmonyOS 的完整过程。
这个库极小:整个包只有 2 个 JS 文件(Button.js 150 行 + coalesceNonElementChildren.js 31 行),源码直发。但它有两个地方值得认真验:
① 它在非 iOS 上走的是 TouchableNativeFeedback
if (Platform.OS === 'ios') {
return <TouchableOpacity … />;
} else {
const background = this.props.androidBackground
? this.props.androidBackground
: TouchableNativeFeedback.SelectableBackground(); // ← 无条件调用
return (
<View style={containerStyle}>
<TouchableNativeFeedback … >
<View style={{padding}}>{this._renderGroupedChildren()}</View>
</TouchableNativeFeedback>
</View>
);
}
实测 Platform.OS === 'harmony' ⇒ 鸿蒙上走的就是 TouchableNativeFeedback 分支。
⇒ "这个按钮在鸿蒙上能不能点"就成了必须实测的问题(不是看文档能解决的)。
② 我在这里差点写下一个错误的结论
我第一次点击按钮时,日志里什么都没有 ⇒ 看起来像"鸿蒙上点不动"。
但我没有直接写这个结论,而是:
- 先核对点击坐标(从布局 dump 里取真实可点节点的中心,而不是自己按标记位置推算);
- 再建三个对照组 —— 直接用
TouchableOpacity/TouchableNativeFeedback/Pressable。
结果:
| 实例 | 组件 | 点击结果 |
|---|---|---|
| B1 | 库的 Button(启用) | ✅ 触发 onPress(并且 onPressIn / onPressOut 也触发) |
| B2 | 库的 Button(disabled) | ✅ 不触发(源码门控生效) |
| C1 | 直接用 TouchableOpacity | ✅ 触发 |
| C2 | 直接用 TouchableNativeFeedback | ✅ 触发 |
| C3 | 直接用 Pressable | ✅ 触发 |
⇒ 结论恰好相反:这个按钮在鸿蒙上工作正常。 我那次"没反应"是假阴性(点击坐标取自推算值、且中途还有一次漏了 force-stop 导致页面没切换)。
如果没有对照组和 dump 复核,我就会把"点了没反应"当成一个"鸿蒙适配问题"写进文章。
设备侧断言 28 / 28 全部通过(A 23 + B 3 + C 2)。

一、先说结论
| 项 | 结果 |
|---|---|
| 上游最新版 | 3.1.0;license: MIT;上游 star 767 |
| 库类型 | 纯 JS 单组件库(class 组件):Button.js 150 行 + 一个 children 合并工具 |
| 是否需要原生适配 | 不需要(无 harmony/、无 NativeModules) |
| 运行时依赖 | ⚠️⚠️ 两个:prop-types@^15.5.10(宿主已有)+ deprecated-react-native-prop-types@^3.0.1(宿主没有) ⇒ 目录/file: 接入会打包失败(本轮实测) |
prepare 家族 | ✅ 没有 |
main | Button.js(源码直发,交付包只有 2 个 JS 文件) |
| 导出形态 | export default class Button ⇒ 默认导出是 class(typeof 'function'、prototype.render 存在) |
| 需要 HAR / 权限 / ohpm | ❌ 都不需要 |
| ★ 鸿蒙上的渲染分支 | Platform.OS === 'harmony' ⇒ 非 iOS 分支 ⇒ View > TouchableNativeFeedback |
| ★★ 可点性(实测) | ✅ 能点:onPress / onPressIn / onPressOut 都触发;disabled 时不触发 |
| ★ 像素判据 | 启用文字 #007aff(蓝);disabled 文字 #dcdcdc(浅灰)⇒ 一对二值判据 |
| ★ 一个静默失效 | {...TouchableOpacity.propTypes} 在 RN 0.84 上贡献 0 个键(实测 propTypes 只有 9 个) |
| 设备侧断言 | ✅ 28 / 28 通过(A 23 + B 3 + C 2) |
二、判定与接入
| 步 | 做法 | 结果 |
|---|---|---|
| ① | package.json 里有没有 harmony.autolinking | 没有 |
| ② | 仓库里有没有 harmony/ | 没有 |
| ③ | 代码里有没有 NativeModules / requireNativeComponent | 全无 |
| ④ | import 图体检 | ./coalesceNonElementChildren + prop-types + deprecated-react-native-prop-types + react / react-native ⇒ 两个真依赖都要查宿主 |
| ⑤ | scripts 扫 prepare 家族 | ✅ 没有 |
npm install "git+https://atomgit.com/oh-react-native/react-native-button.git#3.1.0-ohos-1.0.0" --ignore-scripts
# ★★ 关键:两个运行时依赖,目录/软链接入都不会自动装
npm install prop-types --ignore-scripts
npm install deprecated-react-native-prop-types@^3.0.1 --ignore-scripts
// metro.config.js
watchFolders: [ path.resolve(__dirname, '../react-native-button') ],
import ButtonTestApp from './ButtonTestApp';
AppRegistry.registerComponent('ButtonTestApp', () => ButtonTestApp);
hdc shell aa force-stop com.rnoh084.demo
hdc shell aa start -b com.rnoh084.demo -a EntryAbility --ps rnAppKey ButtonTestApp
不需要 HAR、权限、ohpm、link-harmony。
三、读源码取到的真值(Button.js,150 行)
3.1 结构
import PropTypes from 'prop-types';
import { ViewPropTypes, TextPropTypes } from 'deprecated-react-native-prop-types';
import React, { Component } from 'react';
import { Platform, StyleSheet, Text, TouchableOpacity, TouchableNativeFeedback, View } from 'react-native';
export default class Button extends Component {
static propTypes = {
...TouchableOpacity.propTypes, // ★ 见 3.2
accessibilityLabel: PropTypes.string,
allowFontScaling: TextPropTypes.allowFontScaling,
containerStyle: ViewPropTypes.style,
disabledContainerStyle: ViewPropTypes.style,
disabled: PropTypes.bool,
style: TextPropTypes.style,
styleDisabled: TextPropTypes.style,
childGroupStyle: ViewPropTypes.style,
androidBackground: PropTypes.object,
};
3.2 ★ 一个静默失效:...TouchableOpacity.propTypes
实测 propTypes 键数 = 9,且键正好是上面那 9 个显式声明的:
propTypes 键 = accessibilityLabel, allowFontScaling, androidBackground, childGroupStyle,
containerStyle, disabled, disabledContainerStyle, style, styleDisabled
⇒ TouchableOpacity.propTypes 在 RN 0.84 里已不存在(undefined),而 {...undefined} 在 JS 里是合法的(得空对象)⇒ spread 静默贡献 0 个键,不报错。
这条值得记住:老库常靠
...SomeComponent.propTypes继承校验;新 RN 把propTypes移除后,这行会静默失效——不报错、不警告,只是校验变少(也意味着这类"继承来的校验"在新 RN 上不能作为证据)。
3.3 ★★ 渲染分支(决定鸿蒙走哪条路)
render() {
let touchableProps = { activeOpacity: this._computeActiveOpacity() };
let containerStyle = [this.props.containerStyle,
this.props.disabled ? this.props.disabledContainerStyle : null];
if (!this.props.disabled) { // ★ 门控:禁用时【根本不挂事件】
touchableProps.onPress = this.props.onPress;
touchableProps.onPressIn = this.props.onPressIn;
touchableProps.onPressOut = this.props.onPressOut;
touchableProps.onLongPress = this.props.onLongPress;
touchableProps.delayPressIn = this.props.delayPressIn;
touchableProps.delayPressOut = this.props.delayPressOut;
touchableProps.delayLongPress = this.props.delayLongPress;
}
if (Platform.OS === 'ios') {
return <TouchableOpacity …>{this._renderGroupedChildren()}</TouchableOpacity>;
} else {
const background = this.props.androidBackground
? this.props.androidBackground
: TouchableNativeFeedback.SelectableBackground();
let padding = 0;
if (containerStyle[0] && containerStyle[0].padding) { // ★ 布局小动作:把 padding 挪到内层
padding = containerStyle[0].padding;
containerStyle[0] = Object.assign({}, containerStyle[0], {padding: 0});
}
return (
<View style={containerStyle}>
<TouchableNativeFeedback {…touchableProps} background={background} …>
<View style={{padding}}>{this._renderGroupedChildren()}</View>
</TouchableNativeFeedback>
</View>
);
}
}
实测:Platform.OS = harmony;TouchableNativeFeedback 在 RNOH 上是 function;TouchableNativeFeedback.SelectableBackground() 返回一个 object,不抛错 ✓
(Platform.constants 也拿到了:rnohArchitecture, reactNativeVersion, osFullName, deviceType, isTesting, Model。)
3.4 ★ 像素判据的根据
const styles = StyleSheet.create({
text: { color: '#007aff', fontSize: 17, fontWeight: '500', textAlign: 'center' },
disabledText: { color: '#dcdcdc' },
group: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },
});
_computeActiveOpacity() {
if (this.props.disabled) { return 1; }
return this.props.activeOpacity != null ? this.props.activeOpacity : systemButtonOpacity; // 0.2
}
_renderGroupedChildren() {
let style = [styles.text,
disabled ? styles.disabledText : null,
this.props.style,
disabled ? this.props.styleDisabled : null];
let children = coalesceNonElementChildren(this.props.children, (children, index) => (
<Text key={index} style={style} …>{children}</Text>
));
switch (children.length) {
case 0: return null;
case 1: return children[0];
default: return <View style={childGroupStyle}>{children}</View>;
}
}
⇒ 启用 ⇒ 文字蓝 #007aff;disabled ⇒ 文字浅灰 #dcdcdc ⇒ 同文案不同 disabled,颜色相反,可数像素 ✓
四、验证设计
| 判据 | 内容 |
|---|---|
| ① 颜色二值对照 | 同文案、只改 disabled ⇒ 蓝 vs 浅灰 |
| ② 点击行为 | 启用应触发 onPress(+ onPressIn/onPressOut);disabled 应不触发 |
| ③ ★ 对照组 | 同时直接用 TouchableOpacity / TouchableNativeFeedback / Pressable,用来排除"整页点不动" |
| ④ 坐标必须来自 dump | 点位取真实可点节点的中心,不用推算值 |
五、实测结果
5.1 断言 28 / 28
| 组 | 内容 | 结果 |
|---|---|---|
| A 契约 | default 是 class、prototype.render/_renderGroupedChildren/_computeActiveOpacity、propTypes 9 键、Platform.OS、TouchableNativeFeedback 与 SelectableBackground()、两个运行时依赖可加载、兼容包导出 | 23/23 |
| B 渲染与像素 | 全部实例渲染未抛错、标题不与状态栏重合、像素判据设计 | 3/3 |
| C 交互与边界 | 事件记录格式、点击次数记录、边界说明 | 2/2 |
| 合计 | 28/28 |
关键观测值:
default 导出的 typeof = function
propTypes 键数 = 9
propTypes 键 = accessibilityLabel, allowFontScaling, androidBackground, childGroupStyle,
containerStyle, disabled, disabledContainerStyle, style, styleDisabled
★★ Platform.OS = harmony
Platform.constants = rnohArchitecture, reactNativeVersion, osFullName, deviceType, isTesting, Model
TouchableNativeFeedback 的 typeof = function
★ TouchableNativeFeedback.SelectableBackground() = function ⇒ object
prop-types 版本 = 15.8.1
deprecated-react-native-prop-types 版本 = 3.0.2
兼容包导出 = ColorPropType, EdgeInsetsPropType, ImagePropTypes, PointPropType,
TextInputPropTypes, TextPropTypes, ViewPropTypes
5.2 ★ 颜色二值对照(像素)
| 实例 | 配置 | 蓝 #007aff | 灰 #dcdcdc |
|---|---|---|---|
| B1 | 启用 | 4849 | 25 |
| B2 | disabled | 0 | 7633 |
⇒ 启用=蓝、禁用=灰,二值干净 ✓

5.3 ★★ 点击行为 + 对照组(本轮核心)
点位全部取自 dump 里真实 clickable 节点的中心:
[B1] 库的 Button(启用) 点 (660, 1635) 节点 y=1575..1695
⇒ PRESS onPress(启用按钮,第 1 次) ★ 触发
⇒ IN onPressIn / OUT onPressOut ★ 也触发
[B2] 库的 Button(disabled) 点 (660, 1978) 节点 y=1918..2038
⇒ (无新事件) ★ 不触发(门控生效)
[C1] 直接用 TouchableOpacity 点 (660, 916) ⇒ PRESS C1 TouchableOpacity 对照组
[C2] 直接用 TouchableNativeFeedback 点 (660, 1338) ⇒ PRESS C2 TouchableNativeFeedback 对照组
[C3] 直接用 Pressable 点 (660, 1699) ⇒ PRESS C3 Pressable 对照组
事件记录里 6 条(截图可见):
IN onPressIn
OUT onPressOut
PRESS onPress(启用按钮,第 1 次)
PRESS C1 TouchableOpacity 对照组
PRESS C2 TouchableNativeFeedback 对照组
PRESS C3 Pressable 对照组
⇒ 结论:react-native-button 在鸿蒙上可正常点击,disabled 门控也正确;三个对照同时证明整页触摸链路正常,所以这个结论不是"整页点不动"造成的假象 ✓


5.4 UI:标题不与状态栏重合
TITLE measureInWindow y=420 (状态栏高 117) ⇒ 420 > 117,不重合 ✓
六、★ 我差点写下的错误结论(本轮最值得记的一条)
过程:
- 我第一次点 B1(按"标记 y + 80"推算坐标),日志里什么都没有;
- 那一刻"鸿蒙上这个按钮点不动"是一个看起来很合理的结论 —— 因为源码里非 iOS 分支用的
TouchableNativeFeedback在跨平台实现上确实可能不完整; - 但我改成:先从 dump 取真实可点节点中心再点,并加了三个对照组;
- 重测后 B1 触发、B2 不触发、三个对照全触发 ⇒ 结论反转。
为什么会假阴性? 两个独立的流程错误叠在一起:
| # | 错误 | 后果 |
|---|---|---|
| 1 | 点击坐标用推算值(标记 y + 偏移),没用 dump 里真实节点的中心 | 可能落在节点外/被别的视图吃掉 |
| 2 | 有一次启动漏了 aa force-stop,页面没切过去 | 我点的是上一个库的测试页(dump 里发现标题是别的库) |
⇒ 教训:涉及"点击没反应"这类否定性结论时,必须先满足两个前提再下判断 ——(a)点位来自 dump 的真实节点;(b)有对照组证明"整页是能点的"。 否定性结论比肯定性结论更需要证据。
这条和"先建对照组"是同一条纪律的两个方向:肯定性结论要防"假阳性"(比如动画没动却以为动了),否定性结论要防"假阴性"(点歪了却以为点不动)。
七、依赖坑:连续第二轮命中
deprecated-react-native-prop-types 不在宿主里,而 file: 接入不会自动装它 ⇒ 打包直接失败:
error Unable to resolve module deprecated-react-native-prop-types
from E:\rnoh-work\react-native-button\Button.js:
deprecated-react-native-prop-types could not be found within the project or in these directories:
装上 deprecated-react-native-prop-types@3.0.2 后立刻 exit 0 ✓
(它自己还带了 @react-native/normalize-color / invariant / prop-types,随之一起装上。)
八、我自己的其它失误(都在本轮被抓住)
| # | 失误 | 怎么发现的 | 修法 |
|---|---|---|---|
| 1 | 用动态 require:require(\${name}/package.json`)` | Metro 报 Invalid call at line 151: require(...) —— Metro 要求 require 的实参可静态分析 | 改成两条静态 require('xxx/package.json') |
| 2 | 按行号替换代码块时留下一个多余的 } | npm run dev 报 Unexpected token, expected "," | 删掉多余括号 |
| 3 | 打包失败却继续构建 HAP | 构建报了 BUILD SUCCESSFUL(用的是旧 bundle)⇒ 断言日志里根本没有本库的 tag | 分别检查两个退出码,并 grep 产物确认新页面在里面(见下) |
第 3 条正是"打包与构建的退出码必须分别看"那个坑的复现:assembleHap 成功 ≠ 你测的是新代码。
本轮我用的确认手段:在 bundle.harmony.js 里 grep 页面名:
Select-String -Path harmony\entry\src\main\resources\rawfile\bundle.harmony.js -Pattern 'ButtonTestApp'
# 出现 8 次 ⇒ 新 bundle 确实在 HAP 里
九、已知限制与本次验证边界
9.1 用这个库要知道的
| 事项 | 说明 |
|---|---|
| 必须手动装两个依赖 | prop-types + deprecated-react-native-prop-types(后者不装就打包失败) |
...TouchableOpacity.propTypes 已静默失效 | RN 0.84 里 TouchableOpacity.propTypes 是 undefined ⇒ 实测 propTypes 只有 9 键 |
非 iOS 走 TouchableNativeFeedback | 鸿蒙上实测能点(onPress/onPressIn/onPressOut 均触发);但外观反馈(水波纹)来自 SelectableBackground(),视觉表现需自行确认 |
disabled 时事件根本不挂 | 源码 if (!this.props.disabled) ⇒ 不给回调;activeOpacity 也变成 1 |
非 iOS 下 containerStyle.padding 会被挪到内层 | 外层 padding 置 0、内层 <View style={{padding}}> 承接(净效果一致,但节点结构不同) |
默认文字色 #007aff / 禁用 #dcdcdc | 通过 style / styleDisabled 覆盖 |
| children 会被合并 | 连续的非元素 children 会被合成一个 <Text>;多个元素则包一层 View(row) |
9.2 本次验证的边界
onLongPress/delayPressIn/delayPressOut/delayLongPress未验证(只验了onPress/onPressIn/onPressOut);androidBackground自定义背景的视觉/行为未验证(B5 传了Ripple,但 RNOH 无Ripple时我用 try/catch 兜成了undefined⇒ 该实例实际走的是SelectableBackground()分支);disabledContainerStyle/styleDisabled未验证;allowFontScaling未验证;- 多 children 的
childGroupStyle布局未验证(只渲染未断言); containerStyle.padding挪位后的视觉等价性未逐像素比对;activeOpacity的实际视觉反馈未验证(需要按下瞬间截图);- 只在一台模拟器上验证(
Pura X View,density 3,1320×2232),没有真机;未做跨平台对照。
未改动库代码。
十、常见问题
Q1:打包报 Unable to resolve module deprecated-react-native-prop-types?
它带的是真依赖,而目录/file: 接入不会安装目标包自己的 dependencies:
npm install deprecated-react-native-prop-types@^3.0.1 --ignore-scripts
(prop-types 也建议一起装上。)
Q2:这个按钮在鸿蒙上能点吗?
能。 实测:Platform.OS = harmony ⇒ 走非 iOS 分支(内部是 TouchableNativeFeedback),点击能触发 onPress / onPressIn / onPressOut;disabled 时不触发。
我同时用直接使用 TouchableOpacity / TouchableNativeFeedback / Pressable 的三个对照组排除了"整页点不动"的可能。
Q3:为什么 disabled 的按钮点了完全没反应?
这是设计如此:源码里只有 if (!this.props.disabled) 成立时才把 onPress/onPressIn/onPressOut/onLongPress 挂到 touchableProps 上;disabled 时事件根本不挂,并且 activeOpacity 变成 1(无反馈)。
Q4:怎么自定义样式?
<Button
style={{color: '#D0021B', fontSize: 18}} // 文字(启用)
styleDisabled={{color: '#999999'}} // 文字(禁用)
containerStyle={{padding: 12, backgroundColor: '#eef2f7'}}
disabledContainerStyle={{opacity: 0.6}}
childGroupStyle={{flexDirection: 'row'}}
disabled={false}
onPress={() => {}}
>确定</Button>
Q5:多个子元素会怎样?
连续的非元素 children 会被合并成一个 <Text>;如果有多个元素,会包一层 View(styles.group 是 flexDirection: 'row')。
Q6:activeOpacity 默认是多少?
disabled 时为 1;否则取 props.activeOpacity,未传则 0.2。
Q7:为什么 propTypes 比我预期的少?
因为它写了 ...TouchableOpacity.propTypes,而 RN 0.84 里 TouchableOpacity.propTypes 已不存在 ⇒ spread 得空对象、静默失效。实测只有 9 个键(源码里显式声明的那些)。
Q8:这个库需要鸿蒙侧做什么特殊处理吗?
不需要。 纯 JS、无原生模块、不需要 HAR / 权限 / ohpm。注意手动装两个依赖,其余按常规接入即可。
小结
react-native-button 是纯 JS 单组件库(2 个 JS 文件、源码直发)。设备侧 28 / 28 断言通过。
最值得记下来的是这几条:
-
★★ 否定性结论需要更多证据。 我第一次点击"没反应",看起来非常像"鸿蒙不支持
TouchableNativeFeedback";加上对照组(三个 touchable 直接用)并把点位改成 dump 里的真实节点中心之后,结论反转:按钮工作正常。
⇒ 凡是要下"点不动 / 收不到 / 不生效"这类否定判断,先满足两个前提:(a)点位/输入来自权威来源(dump);(b) 有对照组证明环境本身是好的。 -
★ 老库的
...SomeComponent.propTypes会静默失效。 RN 0.84 里TouchableOpacity.propTypes已是undefined,{...undefined}合法 ⇒ 不报错、只是校验变少(实测 propTypes 只有 9 键)。⇒ 这类"继承来的校验"在新 RN 上不能当作证据。 -
★ 依赖坑连续第二轮命中。 这次是
deprecated-react-native-prop-types(宿主没有)⇒ 打包Unable to resolve module直接 exit 1。⇒ 预检的 import 图体检 + 宿主依赖预查,两轮都提前给了信号。 -
★ 打包与构建的退出码必须分别看(坑 154 复现)。 我这轮
npm run dev失败过,但assembleHap依然BUILD SUCCESSFUL(装的是旧 bundle)。⇒ 判据:分别检查两个退出码,并在bundle.harmony.js里 grep 你的页面名确认新代码真的在产物里。 -
★ 页面切换要
force-stop后再启动。 本轮有一轮实验点在了上一个库的测试页上(dump 里发现标题不对)。⇒ 每次实验前先 dump 一眼标题/标记,确认页面确实是目标页。
本篇用到的库
| 项 | 内容 |
|---|---|
| 三方库 | react-native-button(上游 3.1.0 的鸿蒙适配版) |
| 适配仓库 | https://atomgit.com/oh-react-native/react-native-button |
| 适配 TAG | 3.1.0-ohos-1.0.0 |
| 需要 HAR / 权限 / ohpm | 都不需要(纯 JS) |
| 库类型 | 纯 JS 单组件库:跨平台按钮(class 组件,2 个 JS 文件) |
| 运行时依赖 | ⚠️ 两个:prop-types + deprecated-react-native-prop-types ⇒ 目录/file: 接入必须手动补装,否则打包失败 |
prepare / prepublish | 都没有 |
| 许可证 | MIT |
| 上游仓库 | https://github.com/ide/react-native-button(★767) |
| 宿主工程 | RNOH084Demo(测试页 rnAppKey = ButtonTestApp) |
# 从适配仓库安装
npm install "git+https://atomgit.com/oh-react-native/react-native-button.git#3.1.0-ohos-1.0.0" --ignore-scripts
# 本地目录接入(开发常用)—— ★★ 两个依赖都要补
npm install ../react-native-button --ignore-scripts
npm install prop-types --ignore-scripts
npm install deprecated-react-native-prop-types@^3.0.1 --ignore-scripts
// metro.config.js
watchFolders: [ path.resolve(__dirname, '../react-native-button') ],
import Button from 'react-native-button';
// ① 基本用法
<Button
style={{fontSize: 17, color: '#007aff'}}
containerStyle={{padding: 10}}
onPress={() => console.log('pressed')}
onPressIn={() => {}}
onPressOut={() => {}}
>
确定
</Button>
// ② 禁用态(事件根本不会挂上)
<Button disabled styleDisabled={{color: '#dcdcdc'}} onPress={() => {}}>不可用</Button>
// ③ 自定义背景(非 iOS 平台;不传则用 SelectableBackground())
<Button androidBackground={/* 你的 background 对象 */} onPress={() => {}}>带背景</Button>
// ④ 多子元素会被包进一层 row 容器
<Button childGroupStyle={{flexDirection: 'row'}} onPress={() => {}}>
<Text>左</Text>
<Text>右</Text>
</Button>
# 换页启动测试页 —— ★ force-stop 不能省,否则可能仍在旧页面
hdc shell aa force-stop com.rnoh084.demo
hdc shell aa start -b com.rnoh084.demo -a EntryAbility --ps rnAppKey ButtonTestApp
判据的复现方式
# ① 颜色二值对照:启用文字 #007aff(蓝);disabled 文字 #dcdcdc(浅灰)
# ② 点击行为:点位必须取自 dump 里【真实 clickable 节点】的中心
# 注意 rnoh 的 dump 里可点节点只写 `clickable`(没有 =true)
devecocli ui layout | Select-String 'clickable'
# ③ 对照组:直接用 TouchableOpacity / TouchableNativeFeedback / Pressable 各一个,
# 用来排除"整页点不动";再用 dump 确认当前页面标题,避免点到上一个库的页面
# ④ 确认新 bundle 真的进了 HAP(防止"构建成功但装的是旧 bundle")
Select-String -Path harmony\entry\src\main\resources\rawfile\bundle.harmony.js -Pattern 'ButtonTestApp'
验证环境
| 项 | 版本 |
|---|---|
| React Native | 0.84.1 |
| React | 19.2.3 |
| RNOH(npm / ohpm) | @react-native-oh/react-native-harmony / @rnoh/react-native-openharmony 0.84.3 |
| Node.js / npm | v24.14.0 / 11.9.0 |
| DevEco Studio | 26.0.0.621 |
| HarmonyOS SDK | API 26(26.0.0.32) |
| 设备 | HarmonyOS 7.0.0(26.0.0) Beta2 模拟器 Pura X View(ohos-x64),1320×2232,density 3 |
| 本机状态栏高度 | 117px(页面留白取 48vp) |
| 宿主依赖 | prop-types 15.8.1(已有)、deprecated-react-native-prop-types 3.0.2(初始不存在 ⇒ 打包失败 ⇒ 装后恢复) |
| 宿主 HAP 产物 | entry-default-signed.hap |
| 本次构建 | assembleHap 4 分 21 秒(最终一轮) |
| 验证规模 | 设备侧 28 / 28 断言;像素二值对照 2 例(蓝 4849/0、灰 25/7633);点击行为 5 例(库按钮启用/禁用 + 3 个对照);依赖坑 1 例(打包 exit 1 → 装依赖 → exit 0);UI 安全区 1 项 |
相关社区与项目地址
| 项目 | 地址 |
|---|---|
| React Native 鸿蒙官方社区(CPF-RN) | https://atomgit.com/CPF-RN |
| RNOH 框架仓库 | https://atomgit.com/openharmony-RN/ohos_react_native |
| RNOH 三方库使用文档 | https://atomgit.com/OpenHarmony-RN/usage-docs |
| React Native for OpenHarmony 组织 | https://atomgit.com/oh-react-native |
| RN 三方库鸿蒙适配清单 | https://atomgit.com/oh-react-native/rn-ohos-adaptation-overview |
欢迎加入 CPF-RN 鸿蒙社区:https://atomgit.com/CPF-RN
React Native for OpenHarmony 组织:https://atomgit.com/oh-react-native
RN 三方库鸿蒙适配清单:https://atomgit.com/oh-react-native/rn-ohos-adaptation-overview
更多推荐



所有评论(0)