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.styleActionButton.jsL366在模块顶层求值的propTypes对象里ActionButton.propTypes{…buttonTextStyle:Text.propTypes.style,// ★ L366…};而 RN 从 0.62 起就移除了Text.propTypes—— 我在 RN 0.84 上确证过node_modules/react-native/Libraries/Text/Text.js27541 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.jsconst{Text}require(react-native);if(!Text.propTypes){Text.propTypes{style:{}};// 只需要存在且能取 .styleconsole.log([polyfill] NOTE | 已为 RN 的 Text 补回 propTypes…);}执行的顺序极其关键—— 必须在库被 import 之前跑// index.jsimport./polyfills/text-prop-types;// ★ 放在所有页面 import 之前实测第 28 行…importActionButtonTestAppfrom./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 是 classfunction PASS | ★ ActionButtonItem 是 classfunction⇒⇒ 补上之后库正常加载、正常渲染✓✓⚠️这是兼容补丁不是 RN 的正确用法。判断它够不够用关键看那个库是只在propTypes声明里用Text.propTypes还是在渲染路径上用 —— 前者一行补丁就够后者就要单独评估。★★★★ 二、修不了的那一处componentWillReceiveProps已被 React 19 移除ActionButton.jsL42componentWillReceiveProps(nextProps){if(nextProps.resetToken!this.state.resetToken){if(nextProps.activefalsethis.state.activetrue){…收起…}if(nextProps.activetruethis.state.activefalse){…展开…}this.setState({resetToken:nextProps.resetToken});}}⇒它负责受控模式外部用activeresetToken两个 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}驱动在本环境失效⚠️但非受控模式仍然可用✓ —— 点击触发内部的animateButtonref也能调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的defaultPropsgetter 形式NOTE | ActionButtonItem.defaultProps 的键 active, activeOpacity, fixNativeFeedbackRadius, nativeFeedbackRippleColor, spaceBetween, useNativeFeedback★★★ 四、isAndroid false在鸿蒙上是安全的shared.jsexportconstisAndroidPlatform.OSandroid;// 鸿蒙 ⇒ falseexportfunctiongetTouchableComponent(useNativeFeedback){if(useNativeFeedbacktrueisAndroidtrue)returnTouchableNativeFeedback;returnTouchableOpacity;// ★ 鸿蒙走这里安全}exportfunctiontouchableBackground(color,fixRadius){if(isAndroid){…}// ★ 鸿蒙不进returnundefined;// ★ 安全}exportconstshadowStyle{shadowOpacity:.35,…,elevation:5};⇒elevationAndroid 阴影与shadow*iOS 阴影都是平台专属属性⇒鸿蒙上被忽略、不崩✓⇒⇒ 这一处平台判断没有造成任何问题✓★★★ 五、文本级判据两个操作项的标题都渲染出来了✓ AB 项一 w1188 h84 x66 y906 ✓ AB 项二 w1188 h84 x66 y906⇒ActionButtonItem的title确实渲染了✓✓ActionButton本体是绝对定位的浮层按钮文本在屏幕右下角不在正文流里⇒LogBox屏上无通知✓ —— 我原本预期会有Animated: useNativeDriver was not specified警告因为它的Animated.spring(this.anim, {toValue: …})全都没传useNativeDriver但实测屏上没有出现⇒ 如实记录为未观察到该警告 ✓六、接入与预检要点# 装库file: 接入它的 dependencies 只有 prop-types宿主通常已有npminstallgithttps://atomgit.com/oh-react-native/react-native-action-button.git#2.8.5-ohos-1.0.0--ignore-scripts# ★ 然后必须在你入口文件的最前面加一行 polyfill见第一节否则 import 即崩七、交付包质量与基线说明类别结果交付包内容ActionButton.js11459 BActionButtonItem.js5414 Bshared.js1010 Bindex.d.ts⇒ 源码与类型都在 ✓⚠️ 包内含example/50 个文件里大半是 example 目录Android/iOS 工程⇒ 开发时可能有干扰无害spec.json⚠️缺upstreamCommitvalidation⚠️build-pass-device-pending⚠️ 声明的基线reactNative: 0.88.0rnoh: 0.84.4⇒两字段自相矛盾备注库代码零改动我加的是自己的 polyfill没改库关于基线本次验证在RN0.84.1 RNOH0.84.3上完成。⚠️ 这个库有两处版本相关的问题Text.propTypes、componentWillReceiveProps换基线时必须重新评估这两条。八、已知限制与本次验证边界8.1 用这个库要知道的事项说明★★★★★ 直接 import 会崩Text.propTypes.styleL366在 RN ≥0.62 是undefined⇒必须加 polyfill★★★★ 受控模式activeprop失效componentWillReceiveProps被 React 19 移除 ⇒ 用ref或key重挂载代替★★★★ 28 项defaultProps全是常量适合做回归断言buttonText /size 56/spacing 20/position right/offsetX 30…★★★ref可用的公开方法reset()、animateButton()reset实测不抛错★★★isAndroid false无害走TouchableOpacityelevation/shadow*被忽略★★ 两个操作项的title正常渲染文本级判据命中★★ 未观察到useNativeDriver警告它的Animated.spring都没传该参数但屏上没出现警告如实记录★ 包里有example/目录占了大半文件数8.2 本次验证的边界★★★ 没有验证点击展开的真实交互FAB 的展开动画、弧线排布、backdrop遮罩★★★ 受控模式只证伪未验证替代方案ref调reset()/key重挂载都只是建议未实测展开效果★★ 未验证展开后的几何degrees: 45/spacing: 20/size: 56的弧线位置★★ 未验证renderIcon自定义图标★★ 未验证backdrop/backgroundTappable/autoInactive的实际行为★★ 未验证positionleft/verticalOrientationdown未验证hideShadow/ 自定义shadowStyle在鸿蒙上的视觉表现未验证多个ActionButton并存的层级/zIndex未在RN 0.88.0/RNOH 0.84.4上复验未测其它屏幕宽度未做跨平台对照。库代码未改动只新增了我自己的 polyfill 文件。九、常见问题Q1一装就报Cannot read property style of undefined因为ActionButton.jsL366 用了Text.propTypes.style而RN ≥0.62 已移除Text.propTypes。加 polyfill见第一节// polyfills/text-prop-types.jsconst{Text}require(react-native);if(!Text.propTypes)Text.propTypes{style:{}};并在入口文件最前面所有页面 import 之前import ./polyfills/text-prop-types;Q2active{true}传进去没反应因为驱动它的是componentWillReceivePropsReact 19 已移除该生命周期。两个替代方案① 用ref调inst.reset()/inst.animateButton()② 用key重挂载让active作为初始值进构造函数。Q3它在 RN 0.84 上能跑吗加 polyfill 之后能跑契约 28 项defaultProps取值 18 项精确命中、两个操作项标题渲染、ref调reset()不抛错、LogBox无通知。但受控模式失效见 Q2。Q4Platform.OS android的判断会有问题吗不会。鸿蒙上isAndroid false⇒getTouchableComponent()返回TouchableOpacity、touchableBackground()返回undefined都安全elevation/shadow*这些平台专属属性会被忽略。Q5这种 polyfill 什么时候够用、什么时候不够看那个库是只在propTypes声明里用被移除的 API还是在渲染路径上用。前者本库一行补丁就够后者补丁无效需要改库或换库。Q6ref上有哪些可用方法实测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 行的 polyfillText.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返回undefinedelevation/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适配 TAG2.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 ActionButtonTestAppnpminstallgithttps://atomgit.com/oh-react-native/react-native-action-button.git#2.8.5-ohos-1.0.0--ignore-scripts// ① 先放这个文件polyfills/text-prop-types.jsconst{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 positionright 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.styleL366 // ⚠️ componentWillReceiveProps 已被 React 19 移除 ⇒ 受控模式静默失效 // ⚠️ Animated.spring 都没传 useNativeDriver实测屏上未出现警告 // ⚠️ 28 项 defaultPropsbuttonText 、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-bcom.rnoh084.demo-aEntryAbility--psrnAppKey ActionButtonTestApp判据的复现方式# ① polyfill 是否执行hilog 里它自己会打一行 NOTEhdc shellhilog -x | grep polyfill# ② 契约28 项 defaultProps Text.propTypes 是否已补hdc shellhilog -x | grep abc-test|Select-StringPASS|FAIL|SUMMARY|NOTE# ③ 文本级两个操作项的 titledevecocli ui layout|Select-StringAB 项验证环境项版本React Native0.84.1React19.2.3RNOHnpm / ohpmreact-native-oh/react-native-harmony/rnoh/react-native-openharmony0.84.3Node.js / npmv24.14.0/11.9.0DevEco Studio26.0.0.621HarmonyOS SDKAPI 2626.0.0.32设备HarmonyOS 7.0.0(26.0.0) Beta2 模拟器Pura X Viewohos-x641320×2232density 3本机状态栏高度117px 39vp宿主依赖prop-types已有必须加Text.propTypespolyfill宿主 HAP 产物entry-default-signed.hap本次构建assembleHap3 分 40 秒⇒ 一次成功打包与构建退出码都检查了验证规模契约判据 22 条21 PASS 1 条是我自己把 28 数成 29polyfill 生效双证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-RNhttps://atomgit.com/CPF-RNRNOH 框架仓库https://atomgit.com/openharmony-RN/ohos_react_nativeRNOH 三方库使用文档https://atomgit.com/OpenHarmony-RN/usage-docsReact Native for OpenHarmony 组织https://atomgit.com/oh-react-nativeRN 三方库鸿蒙适配清单https://atomgit.com/oh-react-native/rn-ohos-adaptation-overview欢迎加入 CPF-RN 鸿蒙社区https://atomgit.com/CPF-RNReact Native for OpenHarmony 组织https://atomgit.com/oh-react-nativeRN 三方库鸿蒙适配清单https://atomgit.com/oh-react-native/rn-ohos-adaptation-overview