React Native for OpenHarmony 实战:三方库 react-native-action-button 的鸿蒙化适配指南
本文记录把 react-native-action-button(可展开的悬浮操作按钮 FAB)适配到 HarmonyOS 的完整过程。
它做的事:右下角一个圆形按钮,点开后沿弧线/直线弹出若干操作项,每项带标题与图标。
结论:它在本环境里"import 就会崩",但崩溃点只有一行、而且只需一个 20 行的 polyfill 就能修好。
修好之后,29 项契约里 28 项 defaultProps 常量全部精确命中,两个操作项的标题正常渲染,ref 也能调用它的 reset()。
但有一处修不了的语义变化:它依赖的 componentWillReceiveProps 在 React 19 里已被移除 ⇒ 受控模式失效。
★★★★★ 一、它的崩溃点:一行 Text.propTypes.style
ActionButton.js L366(在 模块顶层 求值的 propTypes 对象里):
ActionButton.propTypes = {
…
buttonTextStyle: Text.propTypes.style, // ★ L366
…
};
而 RN 从 0.62 起就移除了 Text.propTypes —— 我在 RN 0.84 上确证过:
node_modules/react-native/Libraries/Text/Text.js(27541 B)里 `propTypes` 命中 = 0
Libraries/Text/*.js 里 "propTypes =" 命中 = 0
RN index.js: get Text() { return require('./Libraries/Text/Text').default; }
⇒ Text.propTypes 是 undefined ⇒ undefined.style ⇒ TypeError ⇒ 库在 import 时直接崩 ✗
★★★★★ 修法:一个 20 行的 polyfill(不改库)
关键判断:这是一行【类型声明】,没有任何渲染逻辑用到它 ⇒ 只要让它能安全求值,库的其余部分完全不受影响:
// polyfills/text-prop-types.js
const {Text} = require('react-native');
if (!Text.propTypes) {
Text.propTypes = {style: {}}; // 只需要"存在且能取 .style"
console.log('[polyfill] NOTE | 已为 RN 的 Text 补回 propTypes…');
}
执行的顺序极其关键 —— 必须在"库被 import 之前"跑:
// index.js
import './polyfills/text-prop-types'; // ★ 放在所有页面 import 之前(实测第 28 行)
…
import ActionButtonTestApp from './ActionButtonTestApp'; // 第 76 行,它内部才 import 那个库
ESM 的
import是按书写顺序执行的(声明会提升,但求值不会)
⇒ 所以这一行在前,就能保证库被 import 时Text.propTypes已经就位 ✓
实测(hilog 里的 polyfill 自己的日志 + 页面上的判据):
10-17 15:28:13.054 I A0beef/#RNOH_JS: __█__ [polyfill] NOTE | 已为 RN 的 Text 补回 propTypes(供依赖 Text.propTypes.style 的老库使用)
PASS | ★★★ polyfill 生效:Text.propTypes 已存在 | 期望=object | 实际=object
PASS | ★★★ polyfill 生效:Text.propTypes.style 已存在 | 期望=object | 实际=object
PASS | ★ 默认导出 ActionButton 是 class(function)
PASS | ★ ActionButtonItem 是 class(function)
⇒ ⇒ 补上之后库正常加载、正常渲染 ✓✓
⚠️ 这是"兼容补丁",不是 RN 的正确用法。
判断它够不够用,关键看那个库是只在propTypes声明里用Text.propTypes,
还是在渲染路径上用 —— 前者一行补丁就够,后者就要单独评估。
★★★★ 二、修不了的那一处:componentWillReceiveProps 已被 React 19 移除
ActionButton.js L42:
componentWillReceiveProps(nextProps) {
if (nextProps.resetToken !== this.state.resetToken) {
if (nextProps.active === false && this.state.active === true) { …收起… }
if (nextProps.active === true && this.state.active === false) { …展开… }
this.setState({ resetToken: nextProps.resetToken });
}
}
⇒ 它负责"受控模式":外部用 active + resetToken 两个 prop 驱动按钮的展开/收起
⇒ 而 React 19 早已移除该生命周期 ⇒ 这段逻辑不会被调用 ✗
实测印证(它确实还挂在 prototype 上,但不会被执行):
NOTE | prototype 上的方法(filter 掉 constructor) =
componentDidMount, componentWillUnmount, componentWillReceiveProps, getOrientation,
getOffsetXY, getOverlayStyles, render, _renderMainButton, _renderButtonIcon,
_renderActions, _renderTappableBackground, animateButton, reset
NOTE | 实例 state 的键 = resetToken, active
PASS | ★ 实例有 state.active
PASS | ★★ ref 调用 reset() 不抛错
⇒ ⇒ 结论:
- 受控模式(
active={true}驱动)在本环境失效 ⚠️ - 但"非受控模式"仍然可用 ✓ —— 点击触发内部的
animateButton,ref也能调reset()/animateButton()✓
用受控模式怎么办? 两个办法:
- 用
ref手动驱动(inst.reset()/inst.animateButton()); - 给组件加
key,用重挂载代替"受控更新"(<ActionButton key={String(active)} active={active} />)——
这样active会作为初始值进入构造函数(this.anim = new Animated.Value(props.active ? 1 : 0))✓
★★★★ 三、28 项 defaultProps 常量逐个核对(其中一项是我数错了)
NOTE | defaultProps 的键数 = 28
NOTE | defaultProps 的键 = accessibilityLabel, accessible, active, activeOpacity, autoInactive,
backdrop, backgroundTappable, bgColor, bgOpacity, buttonColor, buttonText, buttonTextStyle,
degrees, fixNativeFeedbackRadius, nativeFeedbackRippleColor, offsetX, offsetY, onPress,
onPressIn, onPressOn, outRangeScale, position, resetToken, size, spacing, testID,
useNativeFeedback, verticalOrientation
FAIL | ★★ defaultProps 恰好 29 项 | 期望=29 | 实际=28 ← ★ 我数错了
PASS | ★★ buttonText = "+" PASS | ★★ size = 56
PASS | ★ spacing = 20 PASS | ★ position = right
PASS | ★ offsetX = 30 PASS | ★ offsetY = 30
PASS | ★ degrees = 45 PASS | ★ verticalOrientation = up
PASS | ★ useNativeFeedback = true PASS | ★ activeOpacity = 0.85
PASS | ★ active = false PASS | ★ resetToken = null
PASS | ★ backdrop = false PASS | ★ autoInactive = true
PASS | ★ outRangeScale = 1 PASS | ★ bgColor = transparent
PASS | ★ bgOpacity = 1
⇒ 18 项取值全部精确命中 ✓✓
⇒ 但"恰好 29 项"那条 FAIL 是我自己数错了(实际 28 项)✗ —— 如实记录,不改判据去迁就
ActionButtonItem 的 defaultProps(getter 形式):
NOTE | ActionButtonItem.defaultProps 的键 = active, activeOpacity, fixNativeFeedbackRadius,
nativeFeedbackRippleColor, spaceBetween, useNativeFeedback
★★★ 四、isAndroid = false 在鸿蒙上是安全的
shared.js:
export const isAndroid = Platform.OS === "android"; // 鸿蒙 ⇒ false
export function getTouchableComponent(useNativeFeedback) {
if (useNativeFeedback === true && isAndroid === true) return TouchableNativeFeedback;
return TouchableOpacity; // ★ 鸿蒙走这里(安全)
}
export function touchableBackground(color, fixRadius) {
if (isAndroid) { … } // ★ 鸿蒙不进
return undefined; // ★ 安全
}
export const shadowStyle = {shadowOpacity: .35, …, elevation: 5};
⇒ elevation(Android 阴影)与 shadow*(iOS 阴影)都是平台专属属性 ⇒ 鸿蒙上被忽略、不崩 ✓
⇒ ⇒ 这一处"平台判断"没有造成任何问题 ✓
★★★ 五、文本级判据:两个操作项的标题都渲染出来了
✓ AB 项一 w=1188 h=84 x=66 y=906
✓ AB 项二 w=1188 h=84 x=66 y=906
⇒ ActionButtonItem 的 title 确实渲染了 ✓✓
(ActionButton 本体是绝对定位的浮层,按钮文本 '+' 在屏幕右下角,不在正文流里)
⇒ LogBox:屏上无通知 ✓ —— 我原本预期会有 Animated: 'useNativeDriver' was not specified 警告
(因为它的 Animated.spring(this.anim, {toValue: …}) 全都没传 useNativeDriver),
但实测屏上没有出现 ⇒ 如实记录为"未观察到该警告" ✓




六、接入与预检(要点)
# 装库(file: 接入;它的 dependencies 只有 prop-types,宿主通常已有)
npm install "git+https://atomgit.com/oh-react-native/react-native-action-button.git#2.8.5-ohos-1.0.0" --ignore-scripts
# ★ 然后必须在你入口文件的最前面加一行 polyfill(见第一节),否则 import 即崩
七、交付包质量与基线说明
| 类别 | 结果 |
|---|---|
| 交付包内容 | ActionButton.js(11459 B)+ ActionButtonItem.js(5414 B)+ shared.js(1010 B)+ index.d.ts ⇒ 源码与类型都在 ✓ |
⚠️ 包内含 example/ | 50 个文件里大半是 example 目录(Android/iOS 工程)⇒ 开发时可能有干扰,无害 |
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 上完成。
⚠️ 这个库有两处版本相关的问题(Text.propTypes、componentWillReceiveProps),换基线时必须重新评估这两条。
八、已知限制与本次验证边界
8.1 用这个库要知道的
| 事项 | 说明 |
|---|---|
| ★★★★★ 直接 import 会崩 | Text.propTypes.style(L366)在 RN ≥0.62 是 undefined ⇒ 必须加 polyfill |
★★★★ 受控模式(active prop)失效 | componentWillReceiveProps 被 React 19 移除 ⇒ 用 ref 或 key 重挂载代替 |
★★★★ 28 项 defaultProps 全是常量 | 适合做回归断言(buttonText '+'/size 56/spacing 20/position 'right'/offsetX 30…) |
★★★ ref 可用的公开方法 | reset()、animateButton()(reset 实测不抛错) |
★★★ isAndroid = false 无害 | 走 TouchableOpacity,elevation/shadow* 被忽略 |
★★ 两个操作项的 title 正常渲染 | 文本级判据命中 |
★★ 未观察到 useNativeDriver 警告 | 它的 Animated.spring 都没传该参数,但屏上没出现警告(如实记录) |
★ 包里有 example/ 目录 | 占了大半文件数 |
8.2 本次验证的边界
- ★★★ 没有验证"点击展开"的真实交互(FAB 的展开动画、弧线排布、
backdrop遮罩); - ★★★ 受控模式只"证伪"未"验证替代方案"(
ref调reset()/key重挂载都只是建议,未实测展开效果); - ★★ 未验证展开后的几何(
degrees: 45/spacing: 20/size: 56的弧线位置); - ★★ 未验证
renderIcon自定义图标; - ★★ 未验证
backdrop/backgroundTappable/autoInactive的实际行为; - ★★ 未验证
position='left'/verticalOrientation='down'; - 未验证
hideShadow/ 自定义shadowStyle在鸿蒙上的视觉表现; - 未验证 多个
ActionButton并存的层级/zIndex; - 未在
RN 0.88.0/RNOH 0.84.4上复验; - 未测其它屏幕宽度;未做跨平台对照。
库代码未改动(只新增了我自己的 polyfill 文件)。
九、常见问题
Q1:一装就报 Cannot read property 'style' of undefined?
因为 ActionButton.js L366 用了 Text.propTypes.style,而 RN ≥0.62 已移除 Text.propTypes。
加 polyfill(见第一节):
// polyfills/text-prop-types.js
const {Text} = require('react-native');
if (!Text.propTypes) Text.propTypes = {style: {}};
并在入口文件最前面(所有页面 import 之前)import './polyfills/text-prop-types';
Q2:active={true} 传进去没反应?
因为驱动它的是 componentWillReceiveProps,React 19 已移除该生命周期。
两个替代方案:① 用 ref 调 inst.reset() / inst.animateButton();② 用 key 重挂载(让 active 作为初始值进构造函数)。
Q3:它在 RN 0.84 上能跑吗?
加 polyfill 之后能跑:契约 28 项 defaultProps(取值 18 项精确命中)、两个操作项标题渲染、ref 调 reset() 不抛错、LogBox 无通知。
但受控模式失效(见 Q2)。
Q4:Platform.OS === "android" 的判断会有问题吗?
不会。 鸿蒙上 isAndroid = false ⇒ getTouchableComponent() 返回 TouchableOpacity、touchableBackground() 返回 undefined,都安全;
elevation / shadow* 这些平台专属属性会被忽略。
Q5:这种 polyfill 什么时候"够用"、什么时候"不够"?
看那个库是只在 propTypes 声明里用被移除的 API,还是在渲染路径上用。
前者(本库)一行补丁就够;后者补丁无效,需要改库或换库。
Q6:ref 上有哪些可用方法?
实测 prototype 上有:componentDidMount、componentWillUnmount、componentWillReceiveProps(不会执行)、getOrientation、getOffsetXY、getOverlayStyles、render、_renderMainButton、_renderButtonIcon、_renderActions、_renderTappableBackground、animateButton、reset。
其中 reset() 实测可安全调用 ✓
小结
react-native-action-button 是可展开的悬浮操作按钮,代码本身很干净(ListView/createClass/React.PropTypes/ViewPropTypes/字符串 ref/this.refs/mixins/getDefaultProps 全部为空),
但它有两处与版本演进撞车的地方,一处能修、一处不能修。
最值得记下来的是这几条:
-
★★★★★ 一个"可修的 import 即崩":
Text.propTypes.style。
RN ≥0.62 移除了Text.propTypes⇒ 模块顶层的Text.propTypes.style⇒ TypeError。
但这是【一行类型声明】 ⇒ 一个 20 行的 polyfill(Text.propTypes = {style: {}})就能修 ✓,
而且是"不改库"的修法 ✓(对比:如果被移除的是整个列表实现,那就只能否决 —— 那种情况补丁无效) -
★★★★★ polyfill 的【执行顺序】是成败关键。
ESM 的import声明会提升、求值不会 ⇒ 把 polyfill 放在所有页面 import 之前(实测第 28 行 / 页面在 76 行),
hilog里能看到它自己的 NOTE,页面判据也确认Text.propTypes已是object✓ -
★★★★ 一处修不了的:
componentWillReceiveProps被 React 19 移除 ⇒ 受控模式失效。
这不是"报错",而是逻辑静默不执行 —— 更隐蔽。
⇒ 替代:ref手动驱动,或key重挂载(让active作为初始值进构造函数)。 -
★★★★ 28 项
defaultProps逐个核对,18 项取值精确命中;但我把"恰好 29 项"断错了(实际 28)。
⇒ 如实记录、不改判据去迁就 —— 契约型断言的价值就在于它会同时暴露"库变了"和"我数错了" ✓ -
★★★
Platform.OS === "android"这类判断在鸿蒙上是安全的:
isAndroid = false⇒ 走TouchableOpacity、touchableBackground返回undefined;elevation/shadow*被忽略 ✓ -
★★ 别把"预期会出现的警告"写成结论。
我本来预期有Animated: 'useNativeDriver' was not specified(它的Animated.spring确实都没传),
但屏上没出现 ⇒ 只能写"未观察到",不能写"没有" ✓ -
★ 包里有
example/目录(50 个文件里大半是它)—— 排查时要记得"这些不是库本体" ✓
本篇用到的库
| 项 | 内容 |
|---|---|
| 三方库 | react-native-action-button(上游 2.8.5 的鸿蒙适配版) |
| 适配仓库 | https://atomgit.com/oh-react-native/react-native-action-button |
| 适配 TAG | 2.8.5-ohos-1.0.0 |
| 需要 HAR / 权限 / ohpm | 都不需要(纯 JS) |
| 库类型 | 可展开的悬浮操作按钮(FAB) |
| 运行时依赖 | prop-types(宿主通常已有) |
| ⚠️ 必须的额外步骤 | 入口最前面加 polyfill 补 Text.propTypes(否则 import 即崩) |
| 导出形态 | 默认导出 ActionButton / ActionButtonItem |
| 许可证 | MIT |
| 上游仓库 | https://github.com/mastermoo/react-native-action-button |
| 宿主工程 | RNOH084Demo(测试页 rnAppKey = ActionButtonTestApp) |
npm install "git+https://atomgit.com/oh-react-native/react-native-action-button.git#2.8.5-ohos-1.0.0" --ignore-scripts
// ① 先放这个文件:polyfills/text-prop-types.js
const {Text} = require('react-native');
if (!Text.propTypes) Text.propTypes = {style: {}};
// ② 入口文件最前面(在所有页面 import 之前)
import './polyfills/text-prop-types';
import ActionButton from 'react-native-action-button';
import ActionButtonItem from 'react-native-action-button/ActionButtonItem';
// 非受控模式(推荐:点击自己展开/收起)
<ActionButton buttonColor="#0b4f9e" position="right" offsetX={30} offsetY={90}>
<ActionButtonItem title="拍照" onPress={() => {}} />
<ActionButtonItem title="相册" onPress={() => {}} />
</ActionButton>
// 受控模式(active prop)在 React 19 上【失效】⇒ 改用 ref 或 key
const ref = useRef(null);
ref.current?.reset(); // 收起
// ⚠️ 不加 polyfill 会 import 即崩:Text.propTypes.style(L366)
// ⚠️ componentWillReceiveProps 已被 React 19 移除 ⇒ 受控模式静默失效
// ⚠️ Animated.spring 都没传 useNativeDriver(实测屏上未出现警告)
// ⚠️ 28 项 defaultProps:buttonText '+'、size 56、spacing 20、position 'right'、offsetX/Y 30、degrees 45
# 换页启动测试页 —— force-stop 不能省
hdc shell aa force-stop com.rnoh084.demo
hdc shell aa start -b com.rnoh084.demo -a EntryAbility --ps rnAppKey ActionButtonTestApp
判据的复现方式
# ① polyfill 是否执行(hilog 里它自己会打一行 NOTE)
hdc shell "hilog -x | grep polyfill"
# ② 契约:28 项 defaultProps + Text.propTypes 是否已补
hdc shell "hilog -x | grep abc-test" | Select-String 'PASS|FAIL|SUMMARY|NOTE'
# ③ 文本级:两个操作项的 title
devecocli ui layout | Select-String 'AB 项'
验证环境
| 项 | 版本 |
|---|---|
| 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 |
| 宿主依赖 | prop-types(已有)+ 必须加 Text.propTypes polyfill |
| 宿主 HAP 产物 | entry-default-signed.hap |
| 本次构建 | assembleHap 3 分 40 秒 ⇒ 一次成功(打包与构建退出码都检查了) |
| 验证规模 | 契约判据 22 条(21 PASS + 1 条是我自己把 28 数成 29);polyfill 生效双证(hilog + 页面);两个操作项标题命中;ref 调 reset() 不抛错;LogBox 无通知;4 张截图互异 |
⚠️ 基线说明:交付包
spec.json声明RN 0.88.0 + RNOH 0.84.4(两字段自相矛盾),本次验证在RN 0.84.1 + RNOH 0.84.3上完成。
⚠️ 本库有两处版本相关问题(Text.propTypes/componentWillReceiveProps)⇒ 换基线时必须重新评估这两条。
相关社区与项目地址
| 项目 | 地址 |
|---|---|
| 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)