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

发布时间:2026/10/24 7:29:35
React Native for OpenHarmony 实战:三方库 react-native-loading-spinner-overlay 的鸿蒙化适配指南 本文记录把react-native-loading-spinner-overlay适配到 HarmonyOS 的完整过程。这个库很小两个源文件、约 3 KB也很纯粹一个全屏的加载遮罩零运行时依赖、没有prepare脚本、连编译产物都与上游逐字符一致。接入本身毫无难度。但它带来的验证问题比接入问题有意思得多它的所有渲染都在 RN 的Modal里transparentstatusBarTranslucent⇒ 要测的是RNOH 的 Modal 半透明遮罩遮罩一起它就把页面上所有触摸都吞掉了—— 这让我原本设计的遮罩显示期间点按钮触发重渲染的实验根本无法执行它是半受控的cancelable被返回键关闭后组件内部认为已关闭、父组件的visible却还是true而库没有任何回调告知父组件。第 3 条是这轮最有价值的发现它是一个真实的行为缺陷而且只靠读源码 一个四步实测就能坐实。一、先说结论项结果上游最新版3.0.1MITgitHead84896fa0284b705f136622dcd0d7f17184f747bb库类型纯 JS 函数组件渲染在 RN 的Modal内是否需要原生适配不需要无harmony/、无NativeModules运行时依赖零dependencies字段不存在prepare/prepack都没有⇒ 安装路径干净与 npm 上游的差异11 / 12 个文件去掉 CR 后逐字符一致含src/**与lib/**编译产物唯一差异是package.json编译assembleHap3 分 52 秒HAP82,083,102 字节32,770设备侧断言12 / 14A 10/10、B 1/1、C 1/1、D 0/2 未验证原因见第八节★ 外部实测全部命中遮罩文字 Y 1281几何预言逐像素吻合rgba(0,0,0,0.25)实测亮度×0.75rgba(255,0,0,1)全屏(254,0,0)含状态栏区域遮罩吞掉全部页面触摸有对照组★ 一个真实的行为缺陷半受控cancelable关闭后组件(false) 与父组件 prop(true)长期不一致且库不接受onRequestClose、无任何回调★ 未验证的一条不传spinnerKey时每次渲染都重挂载 Modal ——代码级事实成立运行时挂载计数本轮没取到原因正是第 5 节那条一句话结论库本身能用、几何与像素都对但要记住它会吞掉页面所有交互以及它的关闭是半受控的。二、判定过程三步走完而且是这一系列里最干净的一个步做法结果①package.json里有没有harmony.autolinking没有②仓库里有没有harmony/没有③代码里有没有NativeModules/requireNativeComponent全无dependencies字段直接不存在 ⇒零运行时依赖。再补两项这个库特有的确认这两项在前面的库里都出过事检查结果为什么值得单独查scripts里有没有prepare/prepack都没有只有build/lint/test有的库在prepare里会删掉随包发布的编译产物导致安装失败、包静默损坏入口在哪main: lib/index.jslib/已提交这类入口是编译产物的包要确认lib/与src/行为一致否则你读的源码不是真正跑的东西关于第二项的核实方式这个包的tsconfig.json写的是target: esnext且没有写module⇒ 编译产物是ESMimport/export default。这意味着在 PC 上用 Noderequire()它是不行的所以我没有做 PC 侧探针而是直接读lib/index.js这份运行时真正执行的代码并逐行核对它与src/index.tsx行为一致 ⇒排除陈旧编译产物这个风险。三、交付包质量3.1 与 npm 上游逐文件比对先去 CR类别结果一致11 个文件src/**、lib/**全部编译产物、README.md、LICENSE、tsconfig.json等不同1 个package.json交付包缺少0交付包多出6 个.gitignore、两个README.OpenHarmony*、代码检查报告、spec.json、契约测试实现与产物双重零改动而且没有prepare⇒ 这是本系列里安装路径最干净的一个。3.2 一个看着像错、查证后没错的地方spec.json里的上游地址写的是gitssh://gitgithub.com/...SSH 形式。第一眼像是不该这么写 —— 但查 npm 的repository字段上游自己就是这么写的⇒不是缺陷。教训名字/格式不一致要先查数据源再定性。3.3 问题问题说明spec.json缺upstreamCommit而 npm明确有gitHead84896fa0…⇒属应修项validation是裸字符串pass没有可复核的测试项清单契约测试是包级 smoke test只检查包名 / 版本 / 主入口存在 / 中文 README 存在一次组件都没渲染过两个交付 README都没写三件事① 组件是半受控的②遮罩会吞掉页面所有触摸③ 不传spinnerKey会重挂载四、几何与像素三条预言全部命中4.1 遮罩文字的位置逐像素吻合源码里textContent的样式是textContent:{fontSize:20,fontWeight:bold,height:50,top:80}而它的容器是全屏绝对定位 justifyContent: center。先算一遍设备 density 3屏高 2232px文字上边 屏幕中心 − 文字半高 top 2232/2 − (50vp/2 × 3) (80vp × 3) 1116 − 75 240 1281实测Text [433,1281,887,1431] SPIN-TEXT-DIM⇒Y1 1281✓ 高度 150px 也正好是50vp × 3✓⇒这个库的布局在 RNOH 上与设计完全一致。4.2 半透明遮罩的变暗比例数值精确overlayColor默认是rgba(0, 0, 0, 0.25)作用在全屏容器上。在同一个像素位置比较无遮罩 / 有遮罩位置下面的页面元素无遮罩有遮罩比值(100,1700) 蓝色按钮#1f6feb(31,111,234)(23,84,177)0.742 / 0.757 / 0.756(660,1700) 页面背景#0f1115(16,17,21)(12,13,17)0.750 / 0.765 / 0.810⇒实测比值 ≈ 0.75 1 − 0.25✓ 遮罩确实是整体压暗 25%而不是别的混合方式。4.3 纯红遮罩顺带证实了statusBarTranslucent把overlayColor换成rgba(255, 0, 0, 1)采样点实测(100,300) / (660,900) / (1200,2100)(254,0,0)(60,60) —— 状态栏区域(254,0,0)遮罩全屏铺满连状态栏区域都被盖住—— 这正好证实了源码里的Modal … statusBarTranslucent{true}是真的生效的。五、遮罩会吞掉页面所有触摸带对照组这一条我先踩坑、后做对照才定性。现象遮罩显示后用工具按坐标点页面上的按钮工具报成功但设备日志里没有任何对应的记录遮罩也没关。对照实验情形结果是否产生日志无遮罩时点「显示 纯红遮罩」红遮罩出现✅ 有遮罩在时点「隐藏全部遮罩」无任何日志遮罩仍在❌ 无⇒Modal窗口把页面所有触摸都吞掉了。两个结论遮罩显示期间用户点不到下面任何东西这是 loading 遮罩的意图但写代码时要意识到加载中禁止操作是自动的不需要额外加pointerEvents⚠️方法论坐标点击工具报成功只代表坐标被点到了不代表按钮被按下。有东西盖着时它照样报成功。以后凡是点了没反应都要先做一次无遮挡时点同一个按钮的对照。六、cancelable的真实触发源是返回键不是点遮罩源码里constclose(){setSpinnerVisibility(false);};const_handleOnRequestClose(){if(cancelable){close();}};Modal onRequestClose{(){_handleOnRequestClose();}}…两个要点组件根本不接受onRequestCloseprop—— 它内部只有一个私有的_handleOnRequestClose从外面传什么都不起作用RN 的Modal.onRequestClose由返回键 / 返回手势触发不是点遮罩触发⇒cancelable必须用返回键来验。实测实例按返回键后结论cancelable未设 false遮罩仍在✓ 与if (cancelable)一致且返回键没有把应用退出被 Modal 消费了cancelable true遮罩关闭✓cancelable经Modal.onRequestClose生效⚠️别把cancelable理解成点遮罩可以关闭。它的实际含义是允许返回键关闭。七、半受控语义本轮最有价值的发现7.1 源码const[spinnerVisible,setSpinnerVisibility]React.useState(visible);// ① 内部自己存一份React.useEffect((){setSpinnerVisibility(visible);},[visible]);// ② 只在 visible 变化时同步constclose(){setSpinnerVisibility(false);};// ③ 关闭只改内部状态⇒cancelable被返回键关闭时只改了组件内部状态没有通知父组件而useEffect的依赖[visible]没变⇒不会重新同步。7.2 四步实测全部命中步骤观测结论① 点「显示 cancelable」遮罩出现文字 Y 1281打开正常② 按返回键遮罩关闭日志里没有父组件的新动作父组件 prop 仍是true③ 让父组件用同样的visible{true}重渲染 3 次遮罩没有重新出现✅边界坐实组件(false) 与父组件(true) 长期不一致④ 把visible走一次 false→true遮罩重新出现✅ 与[visible]依赖一致7.3 为什么这是个缺陷库不提供任何回调告知父组件我被关掉了它连onRequestCloseprop 都不接受⇒ 父组件无从得知。如果它一直保持visible{true}就会长期处于父组件以为在显示、实际已经关了的不一致状态而且遮罩不会自己回来除非父组件主动把visible走一次 false→true。规避做法// 父组件自己掌握状态要用 cancelable 就自己监听返回键 const [visible, setVisible] useState(false); useEffect(() { const sub BackHandler.addEventListener(hardwareBackPress, () { if (visible) { setVisible(false); return true; } // 自己关自己知道 return false; }); return () sub.remove(); }, [visible]); Spinner visible{visible} cancelable{false} textContent加载中 /;八、一条代码级成立、运行时未验证的行为8.1 代码级事实View style{[styles.container, …]} key{spinnerKey || spinner_${Date.now()}}Date.now()每次渲染都不同⇒ 不传spinnerKey时这个key每次渲染都变⇒ 按 React 语义该子树会被卸载再重新挂载。这解释了spinnerKey这个 prop 为什么存在想避免重挂载就得给一个稳定的 key。8.2 为什么本轮没测到我原本的设计是传一个挂载时会打日志的customIndicator然后在遮罩显示期间点重渲染按钮数挂载次数。但这个设计被第 5 节推翻了遮罩一起就吞掉所有触摸 ⇒我没法在遮罩显示期间点到页面按钮⇒ 父组件不会重渲染 ⇒挂载计数拿不到。所以这一轮的 D 组两条断言如实失败✗ [D] 不传 spinnerKey 的实例挂载次数 1每次渲染都重挂载期望 true实际 false ✗ [D] 固定 spinnerKey 的实例挂载次数更少期望 true实际 false · NO-KEY 挂载次数 0 FIXED-KEY 挂载次数 0补测的正确做法在页面里加一个自驱动计时器遮罩显示期间自动触发父组件重渲染这样就不依赖外部点击 —— 但这需要再改一次页面并重编。本轮为控制时长没有做。⇒这一条只能记成代码级事实成立、运行时未验证不能写成已实测。九、已知限制与本次验证边界9.1 用这个库要知道的四件事事项说明安装无prepare普通npm install即可本系列仍统一加--ignore-scripts遮罩会吞掉页面所有触摸显示期间下面的按钮全点不到也点不到遮罩来关闭cancelable 允许返回键关闭不是点遮罩关闭而且不会通知父组件半受控组件自己关掉后父组件的visibletrue不会让它重开要 false→truespinnerKey不给的话每次渲染都会重挂载 Modal 子树代码级运行时未验不接受onRequestCloseprop想在关闭时做事情只能自己在父组件监听返回键9.2 本次验证的边界只在一台模拟器上验证Pura X Viewdensity 31320×2232没有真机。重挂载行为D 组未在运行时验证原因见第八节。animationnone/slide/fade完全未测—— 本轮只用默认的noneModal 动画在 RNOH 上的表现未知。sizesmall与数值型size未测只用了默认large源码注释说不支持normal但没实测传normal会怎样。color指示器颜色未做像素核对本轮只核对了遮罩色。indicatorStyle/textStyle自定义未测。customIndicator/children自定义内容只用于挂载计数没有渲染可测内容的布局/交互。多个 Spinner 同时visible的叠加/层级未测本轮六个实例每次只显示一个。children与customIndicator同时存在时的优先级未做组合实测源码是children || _renderDefaultContent()、customIndicator || ActivityIndicator。visible从 true→false 的关闭时序/动画未测。点遮罩本身是否有其它副作用未细测已确认不会关闭。没有跨平台对照未在 iOS/Android 上跑同一套用例。未改动库代码。十、常见问题Q1怎么接入npminstallgithttps://atomgit.com/oh-react-native/react-native-loading-spinner-overlay.git#3.0.1-ohos-1.0.0--ignore-scripts// metro.config.js —— file: / 软链装法需要把真实目录加进 watchFolderswatchFolders:[path.resolve(__dirname,../react-native-loading-spinner-overlay)],import Spinner from react-native-loading-spinner-overlay; Spinner visible{loading} textContent加载中… /;不需要HAR、权限、ohpm、link-harmony。Q2遮罩显示时我还能操作页面上的按钮吗不能。它渲染在一个全屏的Modal窗口里会吞掉页面所有触摸实测有遮罩时点页面按钮设备日志里没有任何反应。这通常正是你要的效果加载中禁止操作但要知道这个禁止操作是自动发生的你不需要另外加pointerEvents反过来说也别指望点一下遮罩就能关掉它。Q3cancelable是不是点遮罩可以关闭不是。cancelable的真实含义是允许返回键关闭const_handleOnRequestClose(){if(cancelable){close();}};Modal onRequestClose{()_handleOnRequestClose()}…Modal.onRequestClose由返回键/返回手势触发点遮罩不会触发。实测cancelablefalse时按返回键不关cancelabletrue时按返回键关闭。Q4我想在遮罩被关闭时做点事情可以传onRequestClose吗不行这个组件根本不接受这个 prop —— 它内部只有一个私有的_handleOnRequestClose。Q5我传了visible{true}用户按返回键关掉之后为什么它不再出现了因为它内部自己存了一份visible状态cancelable关闭时只改了内部状态const[spinnerVisible,setSpinnerVisibility]useState(visible);useEffect(()setSpinnerVisibility(visible),[visible]);useEffect的依赖是[visible]而父组件传的值一直是true没变⇒不会重新同步⇒ 遮罩保持关闭。实测已坐实父组件用同样的visible{true}重渲染 3 次遮罩也不会回来只有把visible走一次false→true才会重新出现。Q6那正确的用法是什么父组件自己掌握状态并且自己监听返回键不要依赖库的内部行为const [visible, setVisible] useState(false); useEffect(() { const sub BackHandler.addEventListener(hardwareBackPress, () { if (visible) { setVisible(false); return true; } return false; }); return () sub.remove(); }, [visible]); Spinner visible{visible} cancelable{false} textContent加载中… /;Q7spinnerKey是干什么的源码里那一行是key{spinnerKey || spinner_${Date.now()}}不传spinnerKey时key每次渲染都不一样⇒ 按 React 语义这个子树每次渲染都会被卸载再重新挂载造成不必要的开销/闪烁。给它一个固定字符串就能避免。⚠️ 这条是代码级事实本轮的运行时挂载计数没能取到原因见第八节所以我没有把它写成已实测。Q8遮罩的默认样式能改吗可以。overlayColor改全屏底色、color改指示器颜色、size改指示器大小、textContent/textStyle改文字、indicatorStyle改指示器与文字容器的样式。实测overlayColor默认rgba(0, 0, 0, 0.25)会把屏幕整体压暗到 75% 亮度传rgba(255, 0, 0, 1)则整屏变纯红连状态栏区域也盖住因为statusBarTranslucent为 true。Q9size能传normal吗源码注释明确写着size不支持normal类型是small | large | number。本轮只实测了默认的large传normal的实际表现未验证。Q10遮罩打开后我卡住了怎么把它弄掉cancelabletrue的实例按返回键cancelablefalse的实例返回键关不掉这是设计行为页面按钮又被吞了 ⇒ 只能重启应用aa force-stop 重新aa start。这是设计测试页时就要想好的事有覆盖层的组件每种状态都要有退出来的路否则后续步骤会被卡死。小结react-native-loading-spinner-overlay是一个很小、很干净的纯 JS 库零运行时依赖、没有prepare、11/12 个文件含全部编译产物与上游逐字符一致。几何与像素实测全部命中遮罩文字位置误差0 像素、rgba(0,0,0,0.25)的压暗比例实测×0.75、纯红遮罩全屏(254,0,0)且覆盖状态栏。值得记下来的是这几条点了没反应要先做无遮挡的对照。遮罩一起坐标点击工具照样报成功但点击被 Modal 窗口吞掉、什么都没发生。⇒工具报成功 ≠ 按钮被按下先分离没点到和功能没生效。读源码要读运行时真正执行的那一份。这个包main指向ESM 的编译产物target: esnext且没写moduleNode 端require不了⇒ PC 探针这条路走不通只能直接读lib/并核对它与src/一致。这一步顺带排除了陈旧编译产物的风险。cancelable这类 prop 要先确定触发源。我第一版页面给组件传了onRequestClose——它根本不接受而Modal.onRequestClose是返回键触发的不是点遮罩。⇒写交互断言前先确认这个交互由什么事件触发否则会写出一个永远不会触发的断言。半受控是这类组件的隐藏陷阱。组件内部useState(props.x)useEffect([x])同步同时又允许自己改这份状态 ⇒它一旦自己改了父组件就被蒙在鼓里。⇒凡是组件内有useState(props.x)都要专门测组件自己改了这份状态之后父组件的 prop 还有效吗。这里的答案是无效而且库没有任何回调能通知你。有覆盖层的组件测试设计要预留退出路径。遮罩吞触摸 ⇒ 页面按钮全失效 ⇒ 只能靠返回键或重启而cancelablefalse的实例返回键也关不掉。⇒设计测试页时就要想好每种状态怎么退出来。代码级成立和已实测是两件事。key每次渲染都变 ⇒ React 必然重挂载这是确定的语义但我这一轮没能在运行时把挂载次数测出来被遮罩吞触摸挡住了。⇒如实写成代码级事实成立、运行时未验证而不是含糊地当成已验证。本篇用到的库项内容三方库react-native-loading-spinner-overlay上游3.0.1的鸿蒙适配版适配仓库https://atomgit.com/oh-react-native/react-native-loading-spinner-overlay适配 TAG3.0.1-ohos-1.0.0需要 HAR / 权限 / ohpm都不需要纯 JS零运行时依赖库类型纯 JS 函数组件全屏加载遮罩渲染在 RNModal内运行时依赖零dependencies字段不存在prepare/prepack都没有⇒ 安装路径干净许可证MIT上游仓库https://github.com/joinspontaneous/react-native-loading-spinner-overlay上游基线npmgitHead84896fa0284b705f136622dcd0d7f17184f747bb宿主工程RNOH084Demo测试页rnAppKey LoadingSpinnerTestApp# 从适配仓库安装npminstallgithttps://atomgit.com/oh-react-native/react-native-loading-spinner-overlay.git#3.0.1-ohos-1.0.0--ignore-scripts# 或本地 file: 安装装成软链需要配 watchFoldersnpminstall../react-native-loading-spinner-overlay --ignore-scripts// metro.config.js —— file: / 软链装法需要把真实目录加进 watchFolderswatchFolders:[path.resolve(__dirname,../react-native-loading-spinner-overlay)],// 用法父组件自己掌握状态 自己监听返回键不要依赖库的内部行为 import Spinner from react-native-loading-spinner-overlay; import {BackHandler} from react-native; const [loading, setLoading] useState(false); useEffect(() { const sub BackHandler.addEventListener(hardwareBackPress, () { if (loading) { setLoading(false); return true; } return false; }); return () sub.remove(); }, [loading]); Spinner visible{loading} textContent加载中… overlayColorrgba(0, 0, 0, 0.25) // 实测屏幕亮度 ×0.75 color#ffffff sizelarge // 不支持 normal也可传数值 spinnerKeymain-spinner // 建议给固定值避免每次渲染重挂载 cancelable{false} // true 时允许返回键关闭且不会通知父组件 /;# 换页启动测试页force-stop 不能省换页参数只在冷启动生效hdc shell aa force-stop com.rnoh084.demo hdc shell aa start-bcom.rnoh084.demo-aEntryAbility--psrnAppKey LoadingSpinnerTestApp环境搭建本手册的验证环境是React Native for OpenHarmonyRNOH。从零准备开发环境DevEco Studio、HarmonyOS SDK API 26、Node.js、ohpm、hvigorw、模拟器与hdc调试请按 CPF-RN 开发者指南的「环境初始化」操作CPF-RN 开发者指南 · 02-搭建准备 · 环境初始化https://atomgit.com/CPF-RN/docs/blob/main/开发者指南/02-搭建准备/环境初始化.md环境就绪后本次验证使用的具体版本组合见文末「验证环境」一节。验证环境项版本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宿主 HAP 产物entry-default-signed.hap82,083,102 字节本次增量构建assembleHap3 分 52 秒HAP 32,770 字节打包资源 14无新增符合预期验证规模设备侧12 / 14A 10/10、B 1/1、C 1/1、D 0/2 未验证外部含几何逐像素核对 1 次、像素取样 7 点、吞触摸对照 2 组、返回键 2 例、半受控四步、上游 12 个文件逐字符比对欢迎加入 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

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询