资讯动态

React Native for OpenHarmony 实战:三方库 react-native-accordion 的鸿蒙化适配指南

发布时间:2026/10/18 7:36:26 来源:尧图企业网站定制
本文记录把react-native-accordion手风琴/折叠面板适配到 HarmonyOS 的完整过程。它做的事很小一个可点击的头部 一段能展开/收起的内容区展开时高度从 0 动画到内容的实际高度。结论先说这个库在 React 19 上【加载即崩】而且不是改一行能修的。原因不是依赖、不是原生代码而是三种早已被移除的写法同时出现在同一个 89 行的文件里importReact,{PropTypes}fromreact;// ① React.PropTypes 已被移除连具名导入一起失效varAccordionReact.createClass({// ② React.createClass 已被移除mixins:[tweenState.Mixin],// ③ mixins 在 class 里不存在propTypes:{activeOpacity:React.PropTypes.number,// ① 又一处对象字面量先于 createClass 求值content:React.PropTypes.element.isRequired,…},getDefaultProps(){…},// createClass 独有的生命周期getInitialState(){…},// 同上toggle(){…this.tweenState(height,{…})},// mixin 注入的方法render(){return(ViewTouchableHighlight refAccordionHeader…/{/* ④ 字符串 ref */}View refAccordionContentWrapper…View refAccordionContent{/* ④ 又两处 */}四种问题、任何一条都足以让模块加载失败。下面把证据、以及一份可用的现代写法替代实现都记录下来。★★★★ 一、加载即崩的取证容器化 require页面上放一个按钮按下后在try/catch里require(react-native-accordion)require 不抛错返回 undefined ← 它抛了只是没抛到你手上同一时刻 LogBox 整屏红屏Uncaught Error Cannot read property number of undefined ← ★ 特征词是 number anonymous loadModuleImplementation guardedLoadModule metroRequire因果链可以精确到字段propTypes是对象字面量它的成员会在React.createClass(...)被调用之前就求值其中第一个字段就是activeOpacity: React.PropTypes.number而React.PropTypes在 React 19 上已经是undefined⇒undefined.number⇒抛错。⇒ 所以崩溃发生在React.PropTypes上React.createClass根本没轮到执行✓⇒ 特征词number与源码逐字对应✓栈里的guardedLoadModule说明是模块求值期✓顺带记一条通用经验Metro 的guardedLoadModule会把模块求值期的异常报给 LogBox而require()返回undefined而不抛错⇒undefined就是这个模块加载失败了的信号不要以为没报错就是好的。★★★ 二、已移除的写法这份清单又长了这个库把几种不同年代的写法混在了一起写法状态典型报错React.createClass({...})React 16 起弃用、后被移除加载即崩React.PropTypes.*React 15.5 起移出到prop-types包Cannot read property number of undefined字符串refxthis.refs.x早已弃用、后被移除取不到 ref / 抛错mixins: [...]this.tweenState()class 组件不支持 mixins方法不存在⇒ 这类加载即崩的写法并不孤例而且四种可以在同一个文件里共存⇒预扫时必须逐项查、不能只查一种。★★★★ 三、一份可用的替代实现实测 16/16 全通过原库的对外行为其实很简单用现代写法重写不到 100 行class 组件 Animated丢掉createClass、mixins、字符串 ref 和一个 2015 年的依赖。3.1 关键改写点原写法替代写法React.createClass({...})class Accordion extends React.ComponentgetDefaultProps()static defaultProps {…}getInitialState()构造函数里的this.state {…}mixins: [tweenState.Mixin]一个Animated.Value高度动画this.tweenState(height, {…})Animated.timing(this.heightAnim, {toValue, duration, easing, useNativeDriver: false}).start()this.getTweeningValue(height)this.heightAnim直接作为style.heightrefAccordionContentthis.refs.x回调 refref{r (this.contentRef r)}React.PropTypes.*删掉或换prop-types包3.2 ★★★★ 一个真实的环境发现measure()在 RNOH 上给不出高度原库量内容高度用的是_getContentHeight(){if(this.refs.AccordionContent){this.refs.AccordionContent.measure((ox,oy,width,height,px,py){…});}}componentDidMount(){// 原库注释without setTimeout, measure returns 0 for every value.// See https://github.com/facebook/react-native/issues/953setTimeout(this._getContentHeight);}我照做了含setTimeout实测仍然拿不到高度A1.state {isVisible:false,contentHeight:0} ← contentHeight 恒为 0⇒后果内容高度是 0 ⇒ 展开动画的目标值是 0 ⇒永远展不开连expanded初始展开的实例也展不开✗改成onLayout上报自然高度后就正常了但要注意一个先有鸡还是先有蛋的问题如果把内容直接放在height: 0的裁剪容器里 ⇒ 内容自己也是 0 高 ⇒onLayout报出 0 ✗解法把内容用绝对定位渲染在裁剪容器里⇒ 内容始终按自然高度布局 ⇒onLayout才能报出真高度 ✓Animated.View style{{height: this.heightAnim, overflow: hidden}} View style{{position: absolute, left: 0, right: 0, top: 0}} {/* ★ 关键 */} onLayout{e this._onContentLayout(e.nativeEvent.layout.height)} {this.props.content} /View /Animated.View改前改后的对照数据contentHeight展开动画值expanded实例用measure()0✗0 ✗展不开✗用onLayout 绝对定位90✓90精确到达✓正常展开✓⇒ 原库注释里那个 2015 年的老问题measure全返回 0在 RNOH 上依然成立而onLayout是可靠的替代。3.3 实测结果16 / 16 全通过每条判据各自一行日志PASS/FAIL明确采集端直接grep就能拿全证据PASS | 主标题窗口 y(vp) ≥ 39状态栏 117px39vp | 实际48 PASS | 替代实现是 classprototype.open 存在 PASS | prototype.close 存在 / prototype.toggle 存在 / prototype.render 存在 PASS | defaultProps.expanded false PASS | defaultProps.animationDuration 300 PASS | defaultProps.easing linear PASS | defaultProps.underlayColor #000 PASS | A1 初始 isVisible falseexpanded 未传 PASS | A1 初始高度动画值为 0 PASS | A1 已量到内容高度0 ← contentHeight 90 PASS | A1 open 后 isVisible true PASS | A1 动画值已到达内容高度 ← 动画当前值 90精确到达 PASS | A1 close 后 isVisible false PASS | A1 动画值已回到 0 SUMMARY | 合计 16/16 通过失败 0几何层面双向验证布局 dumpA1 区收起 只有 MARK 头部 ⇒ 内容不可见 ✓ A2 区expandedMARK 头部 A2 初始就展开的内容h66⇒ 内容可见 ✓四、接入与预检步做法结果①package.json里有没有harmony.autolinking没有②仓库里有没有harmony//ios//android/都没有③原生痕迹预扫无原生源文件、无NativeModules⇒纯 JS✓④import 图体检reactreact-nativereact-tween-state⇒ ⚠️一个真实的运行时依赖⑤scripts扫prepare家族✅ 没有⑥★ 老 API 预扫命中React.createClass、React.PropTypes含从react具名导入、字符串 ref、mixins⑦平台分支预扫✅ 无一处Platform.OS都没有⚠️ 依赖陷阱务必注意库的dependencies里声明了react-tween-state: 0.0.5而用file:本地目录方式接入时npm 不会自动安装库自己的依赖⇒ 必须显式补装npminstallreact-tween-state0.0.5 --ignore-scripts它自身还会带一个传递依赖tween-functions实测装上的是1.2.0✓react-tween-state本身不含已移除的 API ✓ —— 问题在库自己身上装完之后的真实状态能装、能打包、但一require就崩✓npminstallgithttps://atomgit.com/oh-react-native/react-native-accordion.git#1.0.1-ohos-1.0.0--ignore-scripts# 或本地目录npminstall../react-native-accordion --ignore-scripts五、交付包质量与基线说明类别结果逐字节一致5 / 6.eslintignore、.npmignore、index.js、README.md、src/index.js不同1 个package.json缺少0多出6 个.gitignore、两个README.OpenHarmony*、代码检查报告、spec.json、契约测试✅实现零改动src/index.js与上游 npm 包逐字节一致⚠️spec.json缺upstreamCommit而 npm 的gitHead有值1cc6b8362a34d4fa0d0ec10383d0f6eed0c792a9⇒属应修项⚠️validation: build-pass-device-pending⇒ 交付方设备侧未完成⚠️交付包未修改任何实现⇒它同样在 React 19 上加载即崩本轮的取证就是针对交付包本身做的。★ 关于基线必须说明的一点这个交付包的spec.json声明的是reactNative: 0.88.0rnoh: 0.84.4verification: evidence/batch50/…而本次验证是在RN0.84.1 RNOH0.84.3的宿主上完成的 ——两者不是同一条基线。为什么这不影响结论崩溃的四个原因React.createClass、React.PropTypes、字符串ref、mixins在新版本上同样是移除状态它们与 RN 的小版本无关取证拿到的是模块求值期抛Cannot read property number of undefined这是React 侧的行为不随 RN 版本变化。⇒ 所以本轮的判定该交付包不可用需按第三节重写是跨基线成立的。六、已知限制与本次验证边界6.1 用这个库/看这份交付包要知道的事项说明★ 在 React 19 上不可用四种已移除的写法同时存在加载即崩★★measure()在 RNOH 上取不到高度原库靠它量内容高度 ⇒ 即便改了前三个问题也还展不开★ 必须改用onLayout 绝对定位见 3.2 的对照数据★ 依赖要显式补装react-tween-state0.0.5file:接入不自动装★ 无平台分支、无原生代码纯 JS鸿蒙侧不需要任何特殊处理★ 替代实现保留了原 APIopen()/close()/toggle()仍是原型方法可用 ref 调⚠️onPress的this语义有变化原库用onPress.call(this)替代实现直接调用React 19 下无必要⚠️easing映射表是白名单原库用tween-functions的名字替代实现把常用名映射到Easing.*未覆盖的名字会回退到linear6.2 本次验证的边界原库的渲染完全未验证明知加载即崩故意不触发其渲染measure()给 0 只在 RNOH 模拟器上实测未做真机与其它平台对照动画的逐帧曲线未采集只核对了起点 0、终点 90以及isVisible状态easing的各种取值未逐项验证只用了默认linear替代实现与原库的视觉一致性未逐像素比对高度值一致但间距/边框等未逐项核onPress的this行为差异未实测只是源码层面的说明未验证TouchableHighlight的underlayColor/activeOpacity实际效果未验证快速连点、展开过程中再次点击等边界未在 0.88 / RNOH 0.84.4 上复验交付包声明的基线理由见第五节只在一台模拟器上验证Pura X Viewdensity 31320×2232没有真机。未改动库代码交付包的src/index.js与上游逐字节一致我只在旁边新增了自己的替代实现。七、常见问题Q1为什么一require就崩到底卡在哪一句卡在对象字面量的求值上而不是createClass上varAccordionReact.createClass({propTypes:{activeOpacity:React.PropTypes.number,// ← 先算这个React.PropTypes在 React 19 上是undefined⇒undefined.number⇒Cannot read property number of undefined⇒ 栈停在loadModuleImplementation → guardedLoadModule。Q2我require它没报错它抛了只是没抛到你手上。Metro 的guardedLoadModule把异常报给 LogBox而require()返回undefined。⇒undefined就是加载失败的信号。Q3改一行能修好吗不能。至少要动四处React.PropTypes.*⇒ 删掉或换prop-types包React.createClass({...})⇒ 改写成 class或函数组件mixins: [tweenState.Mixin]⇒ class不支持 mixins得自己实现建议直接换成Animated字符串refxthis.refs.x⇒ 换成回调 ref而且改完这四处还会遇到第五个坑measure()取不到高度见 Q4。Q4为什么我重写成 class 之后还是展不开因为内容高度是 0 ⇒ 动画目标是 0。原库用measure()量高度而实测measure()在 RNOH 上回调里一直是 0原库注释里那句 “without setTimeout, measure returns 0” 的老问题依然成立加setTimeout也没用。⇒改用onLayout并让内容绝对定位在裁剪容器里否则容器高度为 0 时内容也量不出真高度。Q5那我还需要用react-tween-state吗不需要。它的作用就是给this注入tweenState()/getTweeningValue()两个方法配合mixins。改用Animated之后连这个 2015 年的依赖以及它的tween-functions子依赖都可以不要了。Q6expanded是干嘛的初始是否展开。替代实现里由构造函数读取this.state{isVisible:!!props.expanded,contentHeight:0};实测expanded实例的内容在 dump 里可见h66未传的实例内容不可见 ✓Q7open()/close()/toggle()还能用吗能 —— 替代实现把它们保留为原型方法AccordionReplacement.prototype.open等可以ref调用。这比模拟点击更稳判据不依赖点击定位。Q8这个库需要鸿蒙侧做什么特殊处理吗不需要。纯 JS、无原生代码、无平台分支 —— 障碍全在 JS 侧四种已移除的写法 measure()量不到高度。小结react-native-accordion是 89 行的手风琴组件纯 JS、无原生代码但在本轮基线以及它自己声明的基线上都不可用。最值得记下来的是这几条★★★★ 四种已移除的写法可以同时出现在一个文件里。React.createClass、React.PropTypes、字符串refthis.refs、mixins——任何一条都足以让加载失败⇒ 预扫必须逐项查全不能只查一种。★★★★ 崩溃点可以精确到字段而且顺序有讲究。propTypes是对象字面量在createClass(...)被调用之前就求值完 ⇒ 崩在React.PropTypes.number第一个字段上createClass根本没轮到执行⇒ 特征词number与源码逐字对应✓★★★require返回undefined不等于没问题—— 异常被guardedLoadModule收进 LogBox 了。undefined就是加载失败的信号栈里的guardedLoadModule说明是模块求值期。★★★★ 一个真实的环境发现measure()在 RNOH 上给不出高度。原库注释里那句 2015 年的老问题facebook/react-native#953在 RNOH 上依然成立加setTimeout也没用。⇒ 可靠解法onLayout上报自然高度 内容用绝对定位渲染在裁剪容器里否则容器 0 高时内容也量不出。对照数据改前contentHeight0展不开→ 改后90精确到达✓★★★ 依赖要用file:接入时库自己的依赖不会自动装。这个库声明了react-tween-state0.0.5连带tween-functions必须显式npm install补上否则连打包都过不去。★★ 判据要逐条直写日志。本轮 16 条判据全部以PASS|描述|期望|实际的独立行输出采集端grep即可拿全证据 ——不要把证据能不能留下来绑在页面的汇总面板上。★ 基线要说清楚。交付包声明RN 0.88.0 RNOH 0.84.4而验证在RN 0.84.1 RNOH 0.84.3上完成但四种已移除写法与 RN 小版本无关⇒ 结论跨基线成立 ✓本篇用到的库项内容三方库react-native-accordion上游1.0.1的鸿蒙适配版适配仓库https://atomgit.com/oh-react-native/react-native-accordion适配 TAG1.0.1-ohos-1.0.0需要 HAR / 权限 / ohpm都不需要纯 JS库类型手风琴 / 折叠面板在 React 19 上不可用运行时依赖⚠️react-tween-state0.0.5连带tween-functions1.2.0⇒file:接入需手动补装prepare/postinstall✅ 都没有许可证ISC上游仓库https://github.com/naoufal/react-native-accordion★435pushed 2018宿主工程RNOH084Demo测试页rnAppKey AccordionTestApp# 从适配仓库安装⚠️ 必须补装它自己的运行时依赖npminstallgithttps://atomgit.com/oh-react-native/react-native-accordion.git#1.0.1-ohos-1.0.0--ignore-scriptsnpminstallreact-tween-state0.0.5 --ignore-scripts# ★ file:/本地目录接入不会自动装// ★ 原库在 React 19 上加载即崩下面是【替代实现】的用法AccordionReplacement.tsx import Accordion from ./AccordionReplacement; const ref useRef(null); Accordion ref{ref} expanded{false} // 初始是否展开实测expanded 实例内容可见 animationDuration{300} easinglinear // 白名单映射到 Easing.* underlayColor#0b4f9e onPress{() console.log(头部被点)} header{Text style{{color: #fff}}▶ 头部点击可切换/Text} content{ View Text内容第一行/Text Text内容第二行/Text /View } / // 编程控制原型方法可用 ref 调 // ref.current.open(); ref.current.close(); ref.current.toggle(); // ⚠️ 原库的四处已移除写法React.createClass / React.PropTypes / 字符串 ref / mixins // ⚠️ 原库用 measure() 量内容高度 ⇒ RNOH 上取到的恒为 0展不开 // 替代实现改为 onLayout 内容绝对定位见正文 3.2 // ⚠️ Animated 驱动高度是布局属性 ⇒ useNativeDriver 必须为 false# 换页启动测试页 —— force-stop 不能省hdc shell aa force-stop com.rnoh084.demo hdc shell aa start-bcom.rnoh084.demo-aEntryAbility--psrnAppKey AccordionTestApp判据的复现方式# ① 加载崩溃取证受控 require放在截图之后再按 —— 会整屏红屏hdc shelluitest uiInput click x yhdc shellhilog -x | grep accordion-test|Select-StringPROBE# ⇒ require 不抛错返回 undefineddevecocli ui layout|Select-Stringlogbox_message_contents_text|logbox_stack_frame_text# ⇒ Cannot read property number of undefined# ⇒ anonymous → loadModuleImplementation → guardedLoadModule → metroRequire# ② 替代实现的判据逐条 PASS/FAIL 日志见正文 3.3hdc shellhilog -x | grep accordion-test|Select-StringPASS|FAIL|SUMMARY# ③ 几何收起/展开的内容可见性devecocli ui layout|Select-StringMARK-A1|MARK-A2|初始就展开的内容# ④ 内容高度优先看 contentHeight改 onLayout 前后是 0 → 90验证环境项版本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宿主依赖⚠️需补装react-tween-state0.0.5连带tween-functions1.2.0宿主 HAP 产物entry-default-signed.hap本次构建assembleHap4 分 05 秒含替代实现的两次构建先 12/16修好onLayout后16/16验证规模容器化 require 取证 1 次LogBox 调用栈替代实现 16 条判据全通几何双向验证 2 组收起/展开内容高度对照 2 组0 → 904 张截图互异交付包逐字节 diff 5/6 一致⚠️基线说明本交付包spec.json声明RN 0.88.0 RNOH 0.84.4本次验证在RN 0.84.1 RNOH 0.84.3上完成因四种已移除写法与 RN 小版本无关结论跨基线成立详见第五节。相关社区与项目地址项目地址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

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑