React Native for OpenHarmony 实战:三方库 react-native-modal-dialog 的鸿蒙化适配指南
本文记录把 react-native-modal-dialog(对话框:Alert / Confirm / Prompt)适配到 HarmonyOS 的完整过程。
它做的事:提供一个 Dialog 基类,并派生出 Alert / Confirm / Prompt 三种对话框,通过实例方法 alert() / confirm() / prompt() 弹出来。
结论:能跑 —— alert() 成功切换了内部状态、全屏层也渲染了;但本文发现了一个真实的库缺陷:hide() 方法根本不存在,而它的三个默认回调都在调用它。
另外这个库踩了 ViewPropTypes 这个坑(RN 0.62+ 已移除)—— 我用扩展 polyfill 的方式解决了,这正好验证了"兼容层可扩展"。
★★★★★ 一、会崩的地方:ViewPropTypes.style(以及我如何扩展 polyfill)
index.js L7-L10:
import {
StyleSheet, Dimensions,
Text, View, TouchableWithoutFeedback, TextInput, ScrollView,
BackHandler,
ViewPropTypes, // ★ RN 0.62+ 已移除这个导出
} from 'react-native';
const StylePropType = ViewPropTypes.style; // ★ L10,模块加载期求值 ⇒ undefined.style ⇒ TypeError
⇒ 库 import 即崩 ✗
修法:扩展已有的 polyfill
我之前为另一个依赖 Text.propTypes.style 的库写过一份 polyfills/text-prop-types.js。
这一轮在同一个文件里加了 ViewPropTypes 的补齐(关键是要覆盖 react-native 模块上的同名 getter):
const PLACEHOLDER_PROP_TYPES = {style: {}};
// ① Text.propTypes
if (!Text.propTypes) Text.propTypes = {...PLACEHOLDER_PROP_TYPES};
// ② ★ ViewPropTypes 本身(RN 0.62+ 已从 react-native 的导出里移除)
const RN = require('react-native');
if (!RN.ViewPropTypes) {
Object.defineProperty(RN, 'ViewPropTypes', {
configurable: true, enumerable: true,
get() { return {...PLACEHOLDER_PROP_TYPES}; },
});
}
// ③ 顺手补上 View / Image / ScrollView 的 propTypes
实测(hilog 里 polyfill 自己打的 5 行):
[polyfill] NOTE | 已为 Text 补回 propTypes(供依赖 Text.propTypes.style 的老库使用)
[polyfill] NOTE | 已为 react-native 的 ViewPropTypes 补上导出(供 `ViewPropTypes.style` 使用) ← ★ 本轮新增
[polyfill] NOTE | 已为 View 补回 propTypes
[polyfill] NOTE | 已为 Image 补回 propTypes
[polyfill] NOTE | 已为 ScrollView 补回 propTypes
页面判据确认:
NOTE | ★ RN.ViewPropTypes 是否存在(polyfill 后应为 object) = object
PASS | ★★★ polyfill 生效:ViewPropTypes 已存在
PASS | ★★★ polyfill 生效:ViewPropTypes.style 可取
⇒ ⇒ 库成功 import、页面正常挂载 ✓✓
⇒ ⇒ 这说明"一份 polyfill 可以按需扩展" —— 它修的是同一类问题(RN 移除的"运行时类型声明入口"),
所以遇到新的同类库,只要在这个文件里加一段即可 ✓
★★★★★ 二、发现一个真实的库缺陷:hide() 方法不存在
Dialog.defaultProps 的三个默认回调全都调用 this.hide()(源码 L35-43):
static defaultProps = {
posText: 'OK',
onNegClick: function () { this.hide(); }, // ★
onPosClick: function () { this.hide(); }, // ★
onCancel: function () { this.hide(); return true; }, // ★
};
但 Dialog 类里没有任何 hide 方法。实测:
const inst = dlgRef.current;
inst.alert('MKD 消息一', {title: 'MKD 标题一'}); // ✓ 调用成功
inst.hide(); // ✗ 抛错
NOTE | alert() 调用 = ok
PASS | ★★★ alert() 之后 state.hidden = false(已显示)
NOTE | hide 抛错 = undefined is not a function ← ★★
FAIL | ★★ ref 调用 hide() 不抛错 | 期望=true | 实际=false
实例的 own keys 也印证了:
props, context, refs, updater, _lockQueue, state, onCancel, params,
_reactInternals, _reactInternalInstance
⇒ 没有 hide(alert 是原型方法,所以不在 own keys 里,但能用)
⇒ 所以这个库的"默认行为"是坏的:
- 点 OK(
posText)⇒ 调onPosClick⇒this.hide()⇒ 抛错 ✗ - 点 取消(
negText)⇒ 调onNegClick⇒this.hide()⇒ 抛错 ✗ - 点遮罩(
onCancel)⇒this.hide()⇒ 抛错 ✗
使用建议:必须自己传 onPosClick / onNegClick / onCancel,并且在里面用别的方式关闭
(例如给 Dialog 传一个自己的 hide,或者干脆用 state 受控 —— 但这个库的 render() 读的是 this.params,受控改造比较麻烦)。
⚠️ 这条是静态 + 运行时双重确认的:
① 源码里Dialog类没有hide方法定义;② 运行时inst.hide抛undefined is not a function;
③ 实例 own keys 里也没有hide。三点一致 ⇒ 结论可靠 ✓
★★★★ 三、继承关系可以精确断言
源码(L12 / L141 / L150 / L160):
export default class Dialog extends Component { … }
export class Alert extends Dialog { … }
export class Confirm extends Dialog { … }
export class Prompt extends Dialog { … }
实测:
PASS | ★★ Alert 是 class(function)
PASS | ★★★ Alert.prototype instanceof Dialog ← 期望=true | 实际=true
PASS | ★★★ Confirm.prototype instanceof Dialog ← 期望=true | 实际=true
PASS | ★★★ Prompt.prototype instanceof Dialog ← 期望=true | 实际=true
⇒ X.prototype instanceof Dialog 这个断言写法很干净 ✓
(比 X.super_ / Object.getPrototypeOf(X) === Dialog 更直观)
static inject 四个类都有:
PASS | ★ Dialog / Alert / Confirm / Prompt 都有 static inject(函数)
★★★ 四、propTypes / defaultProps 逐项核对(我又把一个数错了)
NOTE | Dialog.propTypes 的键数 = 14
NOTE | Dialog.propTypes 的键 = buttonBarStyle, buttonStyle, contentStyle, dialogStyle, message,
messageStyle, modalStyle, negText, onCancel, onNegClick, onPosClick, posText, title, titleStyle
FAIL | ★★ Dialog.propTypes 有 15 项 | 期望=15 | 实际=14 ← ★ 我数错了
NOTE | Dialog.defaultProps 的键数 = 4
NOTE | Dialog.defaultProps 的键 = onCancel, onNegClick, onPosClick, posText
PASS | ★★ Dialog.defaultProps 有 4 项
PASS | ★★ defaultProps.posText = OK
NOTE | Alert.defaultProps 的键 = onCancel, onNegClick, onPosClick, posText, title
PASS | ★★ Alert.defaultProps.title = Alert
PASS | ★★ Alert.defaultProps.posText 继承自 Dialog(OK) ← ★ 展开运算符的继承也验证了
NOTE | Confirm.defaultProps 的键 = negText, onCancel, onNegClick, onPosClick, posText, title
PASS | ★★ Confirm.defaultProps.title = Confirm
PASS | ★★ Confirm.defaultProps.negText = Cancel
NOTE | Prompt.propTypes 的键 = onChangeText, placeholder
PASS | ★★ Prompt.propTypes 有 2 项
NOTE | Prompt.defaultProps 的键 = children, message, negText, onCancel, onNegClick, onPosClick, posText, title
PASS | ★★ Prompt.defaultProps.title = Prompt / negText = Cancel
PASS | ★★ Prompt.defaultProps.children 是长度 2 的稀疏数组([,,])
⇒ Dialog.propTypes 实际 14 项,我写成 15 ✗ —— 这是连续第三轮在"数量类断言"上数错
⇒ Alert.defaultProps 有 5 个键(4 个继承 + title) ⇒ {...Dialog.defaultProps, title:'Alert'} 展开继承被验证 ✓
★★★ 五、状态机与渲染观察
render() 开头就很关键(L44-47):
render() {
if (this.state.hidden) {
return null; // ★ 初始什么都不渲染
}
…
}
实测状态机:
NOTE | 实例 state 的键 = hidden
NOTE | 实例 state.hidden = true
PASS | ★★★ 初始 state.hidden = true(所以 render 返回 null)
NOTE | alert 之后 state.hidden = false
PASS | ★★★ alert() 之后 state.hidden = false(已显示)
⇒ alert() 成功把 hidden 置为 false ✓
但内容文本没在布局 dump 里观察到 ⚠️:
- 我在
alert()之后重新采样 dump,含MKD的节点只有我自己页面上的说明文字 - 但找到了两个全屏层:
w=1320 h=2115与w=1320 h=2061 - 这与源码
styles.dialog的position:'absolute' + width:window.width + height:window.height完全吻合
⇒ ⇒ 结论:对话框的"全屏容器"确实渲染了,但它的内容文本(title / message / OK 按钮)本轮没能在 dump 里定位到 ⚠️
⇒ 我不把"文本渲染了"写成结论,只写"容器渲染、内容未观察到" ✓
★★ 六、其余预检结论
| 预检项 | 结果 |
|---|---|
dependencies / peerDependencies | ✅ 都空 |
老写法九项(createClass / React.PropTypes / Text.propTypes / 字符串 ref / this.refs / mixins / componentWillReceiveProps / componentWillMount / getDefaultProps) | ✅✅ 全部为空 |
ListView / measure() / onLayout / useNativeDriver / NativeModules / Platform.OS / isRequired | ✅ 全空 |
Modal 4 处 | ⚠️ 全是 modalStyle / styles.dialogModal 这类命名巧合,没有真的用 <Modal>(本月第四次同类误报) |
| 唯一真坑 | ViewPropTypes.style(见第一节) |
| 外部 import | 只有 prop-types / react / react-native ✓ |
| 交付包形态 | ⚠️ 只有一个 index.js(7852 B,246 行),无 src/、无 .d.ts |




七、接入与预检(要点)
# 装库(无 dependencies、无 peerDependencies)
npm install "git+https://atomgit.com/oh-react-native/react-native-modal-dialog.git#1.2.3-ohos-1.0.0" --ignore-scripts
# ★ 还需要在入口最前面引入 polyfill(补 Text.propTypes 与 ViewPropTypes),否则 import 即崩
八、交付包质量与基线说明
| 类别 | 结果 |
|---|---|
| 交付包内容 | ⚠️ 只有一个 index.js(7852 B / 246 行) ⇒ 没有 src/、没有 .d.ts |
spec.json | ⚠️ 缺 upstreamCommit |
validation | ⚠️ "build-pass-device-pending" |
| ⚠️ 声明的基线 | reactNative: "0.88.0" + rnoh: "0.84.4" ⇒ 两字段自相矛盾 |
| 备注 | 库代码零改动(改的是我自己的 polyfill) |
关于基线:本次验证在 RN 0.84.1 + RNOH 0.84.3 上完成。
⚠️ ViewPropTypes 与 hide() 这两条都是版本相关的(前者随 RN 版本,后者是库自身缺陷)⇒ 换基线需重新确认。
九、已知限制与本次验证边界
9.1 用这个库要知道的
| 事项 | 说明 |
|---|---|
★★★★★ hide() 不存在,但三个默认回调都在调它 | 必须自己传 onPosClick / onNegClick / onCancel,否则点 OK/取消/遮罩都会抛 undefined is not a function |
★★★★★ 必须补 ViewPropTypes | L10 ViewPropTypes.style 在 RN ≥0.62 会抛 TypeError ⇒ import 即崩 |
★★★★ render() 初始返回 null | state.hidden 初始为 true ⇒ 必须调 alert()/confirm()/prompt() 才显示 |
| ★★★ 继承关系可断言 | Alert.prototype instanceof Dialog 等三条全 true |
★★★ Alert.defaultProps 是 {...Dialog.defaultProps, title} | 实测 5 个键(4 继承 + 1 title)⇒ 展开继承被验证 |
★★ Prompt.defaultProps.children 是稀疏数组 [,,] | 长度 2(不是 0) |
★★ props 变化不会反映到渲染 | render() 读的是 this.params(构造时从 props 复制) |
★ Modal 命中的 4 处是命名巧合 | modalStyle / styles.dialogModal,不是 <Modal> |
9.2 本次验证的边界
- ★★★ 未能在 dump 里定位到对话框的内容文本(title / message / OK 按钮)——
只确认了全屏容器渲染(w=1320 h=2115,与styles.dialog的全屏定义吻合); - ★★★ 未验证
confirm()与prompt()的行为(只调了alert()); - ★★ 未验证
Prompt的TextInput与onChangeText; - ★★ 未验证
static inject(ref)的用法(只断言它是函数); - ★★ 未验证
BackHandler(Android 返回键)逻辑; - 未验证 自定义
dialogStyle/modalStyle/contentStyle等 8 个样式 prop; - 未验证
Confirm的双按钮布局与negText; - 未在
RN 0.88.0/RNOH 0.84.4上复验; - 未测其它屏幕宽度;未做跨平台对照。
库代码未改动(改的是 polyfills/text-prop-types.js,属于我自己的兼容层)。
十、常见问题
Q1:它在 RN 0.84 上能跑吗?
加 polyfill 之后能跑:alert() 能把 state.hidden 从 true 置为 false、全屏容器渲染、
继承关系与 defaultProps 继承都对;LogBox 无通知。
但 hide() 不存在(见 Q2),而且内容文本本轮没能在 dump 里定位到。
Q2:点 OK / 取消会报 undefined is not a function?
因为 Dialog.defaultProps 里有:
onNegClick: function () { this.hide(); },
onPosClick: function () { this.hide(); },
onCancel: function () { this.hide(); return true; },
而 Dialog 类里没有 hide 方法 ⇒ 这三个默认回调一执行就炸。
必须自己传:
<Dialog
onPosClick={function () { /* 自己的关闭逻辑 */ }}
onNegClick={function () { /* 自己的关闭逻辑 */ }}
onCancel={function () { /* 自己的关闭逻辑 */ }}
/>
⚠️ 由于 render() 读的是 this.params(构造时快照),"自己维护一个 hidden 状态"这条路不太好走 ⇒
比较现实的做法是给 Dialog 的原型补一个 hide:
import Dialog from 'react-native-modal-dialog';
if (typeof Dialog.prototype.hide !== 'function') {
Dialog.prototype.hide = function () { this.setState({hidden: true}); };
}
Q3:一 import 就报 Cannot read property 'style' of undefined?
index.js L10 const StylePropType = ViewPropTypes.style;,而 RN ≥0.62 已移除 ViewPropTypes。
用 polyfill 把它补上(见第一节;关键是用 Object.defineProperty 覆盖 react-native 模块上的同名 getter)。
Q4:挂了 <Dialog /> 却什么都不显示?
正常 —— render() 第一行就是 if (this.state.hidden) return null;,而 state.hidden 初始为 true。
必须先调实例方法:
ref.current.alert('消息内容', {title: '标题'});
ref.current.confirm('确认吗?');
ref.current.prompt('请输入', '占位符', text => console.log(text));
Q5:怎么确认它真的派生了 Dialog?
Alert.prototype instanceof Dialog; // true
Confirm.prototype instanceof Dialog; // true
Prompt.prototype instanceof Dialog; // true
(比看 super_ 或 Object.getPrototypeOf 直观)
Q6:Prompt 的 children 为什么是 [,,]?
源码里写的是 children: [, ,] —— 一个长度 2 的稀疏数组([undefined, undefined]),
render() 里通过 children.length == 2 && children[0] === undefined && children[1] === undefined 判断"用户没传 children",
然后自己塞进去一个 Text 和一个 TextInput。实测 length === 2 ✓
小结
react-native-modal-dialog 是一个 index.js 单文件的对话框库(Dialog + Alert/Confirm/Prompt),
dependencies 与 peerDependencies 都为空、老写法九项全空。
最值得记下来的是这几条:
-
★★★★★ 一个真实的库缺陷:
hide()不存在,但三个默认回调都在调它。
静态(源码无定义)+ 运行时(inst.hide抛undefined is not a function)+ own keys(没有hide)三重确认 ✓✓
⇒ 影响:点 OK / 取消 / 遮罩的默认行为全部抛错 ⇒ 必须自己传回调或给原型补一个hide。 -
★★★★★ polyfill 可以"按需扩展",而且这次扩展得很干净。
原来只补Text.propTypes;这轮加了ViewPropTypes(用Object.defineProperty覆盖react-native模块上的 getter)+View/Image/ScrollView
⇒ hilog 里 5 行[polyfill] NOTE全部打出、页面判据确认ViewPropTypes已是object✓✓
⇒ 这类补丁修的是"同一类被移除的运行时类型入口",值得放进工程的polyfills/长期保留。 -
★★★★
X.prototype instanceof Dialog是断言继承关系最干净的写法(三条全 true)。 -
★★★★
Alert.defaultProps有 5 个键(4 个继承 + title)
⇒{...Dialog.defaultProps, title:'Alert'}的展开继承行为被验证 ✓ -
★★★ 我又把一个数错了:
Dialog.propTypes实际 14 项、我写成 15。
这是连续第三轮在"数量类断言"上出错(前两次:defaultProps项数、forwardRef类型)。
⇒ 这次的根因很明确:我是手数源码里的字段,而不是直接Object.keys(...).length取真值再写期望。
⇒ 改法:数量类断言必须先在同一环境里取到真值、再把它作为期望(我这一轮其实已经把真值打成了NOTE,
只是写期望时又凭手数了)✓ -
★★★ "全屏容器渲染了、内容文本没观察到"要如实区分。
我找到了w=1320 h=2115的全屏层(与styles.dialog定义吻合)⇒ 容器确实渲染;
但没找到title/message/OK文本 ⇒ 内容只写"未观察到",不写"没渲染" ✓ -
★★
Modal的 4 处命中又是命名巧合(modalStyle/styles.dialogModal)⇒
本月第四次"名字里含已移除 API"的误报,再次印证"要扫真实使用形态"。 -
★★
render()读的是this.params而不是this.props⇒
props 变化不会反映到渲染 ⇒ 想做"受控对话框"会比较别扭 ✓
本篇用到的库
| 项 | 内容 |
|---|---|
| 三方库 | react-native-modal-dialog(上游 1.2.3 的鸿蒙适配版) |
| 适配仓库 | https://atomgit.com/oh-react-native/react-native-modal-dialog |
| 适配 TAG | 1.2.3-ohos-1.0.0 |
| 需要 HAR / 权限 / ohpm | 都不需要(纯 JS) |
| 库类型 | 对话框(Dialog 基类 + Alert/Confirm/Prompt) |
| 运行时依赖 | ✅ dependencies / peerDependencies 都空 |
| 导出形态 | 默认导出 Dialog(class) + 具名 Alert/Confirm/Prompt(都 extends Dialog) |
| ⚠️ 必须的额外步骤 | 入口最前面引入 polyfill(补 Text.propTypes 与 ViewPropTypes) |
| ⚠️ 库缺陷 | hide() 不存在,但 defaultProps 的 3 个回调都调它 ⇒ 必须自己传回调或给原型补 hide |
| 许可证 | MIT |
| 上游仓库 | https://github.com/zhengxiaoyao0716/react-native-modal-dialog |
| 宿主工程 | RNOH084Demo(测试页 rnAppKey = ModalDialogTestApp) |
npm install "git+https://atomgit.com/oh-react-native/react-native-modal-dialog.git#1.2.3-ohos-1.0.0" --ignore-scripts
// ① polyfill(入口最前面,见正文第一节)
import './polyfills/text-prop-types'; // 内部同时补 Text.propTypes 与 ViewPropTypes
// ② 库缺陷补救:给 Dialog 原型补一个 hide(可选,但强烈建议)
import Dialog from 'react-native-modal-dialog';
if (typeof Dialog.prototype.hide !== 'function') {
Dialog.prototype.hide = function () { this.setState({hidden: true}); };
}
import Dialog, {Alert, Confirm, Prompt} from 'react-native-modal-dialog';
const ref = useRef(null);
<Dialog ref={ref} />
// 弹出(render 初始返回 null,必须调方法)
ref.current.alert('消息内容', {title: '标题'});
ref.current.confirm('确认执行吗?', {title: '确认'});
ref.current.prompt('请输入', '占位符', text => console.log(text));
// 关闭(⚠️ 库本身没有 hide,见上面的原型补丁)
ref.current.hide();
// ⚠️ Dialog.propTypes 14 项 / defaultProps 4 项(posText 'OK' + 3 个回调)
// ⚠️ Alert/Confirm/Prompt 都 extends Dialog;Alert.prototype instanceof Dialog === true
// ⚠️ 默认回调调用 this.hide(),而 hide 不存在 ⇒ 不传回调点 OK/取消会抛 undefined is not a function
// ⚠️ render 读的是 this.params(构造时快照)⇒ props 变化不反映
# 换页启动测试页 —— force-stop 不能省
hdc shell aa force-stop com.rnoh084.demo
hdc shell aa start -b com.rnoh084.demo -a EntryAbility --ps rnAppKey ModalDialogTestApp
判据的复现方式
# ① polyfill 是否把 ViewPropTypes 补上
hdc shell "hilog -x | grep '\[polyfill\]'"
# ⇒ 5 行(Text / ViewPropTypes / View / Image / ScrollView)
# ② 契约 + 继承 + 状态机 + hide 缺陷
hdc shell "hilog -x | grep mkd-test" | Select-String 'PASS|FAIL|SUMMARY|NOTE'
# ⇒ Alert.prototype instanceof Dialog = true
# ⇒ 初始 state.hidden = true;alert() 后 = false
# ⇒ hide 抛错 "undefined is not a function"
验证环境
| 项 | 版本 |
|---|---|
| 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 = 39vp |
| 宿主依赖 | ✅ 无额外依赖;需引入扩展后的 polyfill |
| 宿主 HAP 产物 | entry-default-signed.hap |
| 本次构建 | assembleHap 3 分 32 秒 ⇒ 一次成功(打包与构建退出码都检查了) |
| 验证规模 | polyfill 5 行日志全打出(含 ViewPropTypes);契约 17/18(1 条是我数错);继承断言 3/3 命中;状态机 true → false 命中;hide() 缺陷三重确认;全屏容器渲染确认;4 张截图互异 |
⚠️ 基线说明:交付包
spec.json声明RN 0.88.0 + RNOH 0.84.4(两字段自相矛盾),本次验证在RN 0.84.1 + RNOH 0.84.3上完成。
⚠️ViewPropTypes与hide()两条都是版本/库自身相关的,换基线需重新确认。
相关社区与项目地址
| 项目 | 地址 |
|---|---|
| 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)