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

本文记录把 react-native-ratings 适配到 HarmonyOS 的完整过程。

这个库(评分组件,滑动评分 + 点击评分)本身是纯 JS、接入很简单。但它暴露了一个和 React 19 直接相关、而且会让人以为"库坏了"的坑:

同一个库里,两个组件的默认值命运完全不同。

  • Rating 是 class 组件(static defaultProps)⇒ React 19 仍然支持 ⇒ 不传 props 也正常渲染 5 颗星 ✓
  • AirbnbRating 是函数组件 (TapRating.defaultProps)⇒ React 19 已移除函数组件的 defaultProps 支持 ⇒ 不传 props 时一颗星都不渲染(0 颗) ✗

而 RNOH 0.84.3 用的正是 React 19.2.3 。所以如果你照着上游 README 写 <AirbnbRating />,屏幕上会什么都没有。

这一条我用"三区对照 + 数星星"实测坐实了,截图里 A 区是一片空白、B 区 5 颗星,对比非常直接。


一、先说结论

项 结果
上游最新版 8.1.0;license: MIT;npm gitHead 为空
库类型 纯 JS 组件库 (TypeScript 编写):Rating(滑动评分,class)+ AirbnbRating(点击评分,函数组件)
是否需要原生适配 不需要 (无 harmony/、无 NativeModules、不用 vector-icons)
运行时依赖 ⚠️ 有:lodash@^4.17.15
prepare ⚠️ "prepare": "husky install" ⇒ 目录/file: 接入时 npm install 直接失败(见第三节)
main ./dist/index.js(tsc 编译产物,已提交)
与 npm 上游的差异 38 / 39 个文件逐字节一致 ;唯一差异是 package.json
需要 HAR / 权限 / ohpm ❌ 都不需要
设备侧断言 ✅ 15 / 15 全部通过
★ 最重要发现 React 19 下 <AirbnbRating /> 渲染 0 颗星 (函数组件 defaultProps 失效),而 <Rating /> 正常渲染 5 颗星(class 生效)
★ 安装路径差异 README 写的 .tgz 命令可用 ;但目录/file: 接入会因 prepare 直接失败,且 lodash 不会自动装 ------ README 两者都没提
三处"看着像缺陷" 查证后都不是(见第四节)

二、判定过程

步 做法 结果
① package.json 里有没有 harmony.autolinking 没有
② 仓库里有没有 harmony/ 没有
③ 代码里有没有 NativeModules / requireNativeComponent 全无
④ 有没有用 react-native-vector-icons 没有(星星用自带 PNG)

⇒ 纯 JS 。但 dependencies 不是空的:

json 复制代码
"dependencies": { "lodash": "^4.17.15" }

这个库真的会用 lodash (TapRating.tsx 里 _.times(count, ...)、_.map(...))⇒ 不装 lodash 就直接报模块找不到。


三、安装:同一份包,两条路径结果相反

3.1 prepare 会让"目录安装"直接失败

package.json 里有:

json 复制代码
"scripts": {
  "prepare": "husky install",
  "build": "tsc -p tsconfig.json",
  "copy-files": "cp -R ./src/images ./dist/images/",
  "postbuild": "yarn run copy-files"
}

prepare 会在安装目录依赖 / git 依赖 时执行,而 husky 只是 devDependency(消费者那里没有)⇒ 实测:

复制代码
$ npm install            # 目录依赖(file:../lib-copy),不加 --ignore-scripts
npm error code 1
npm error path .../lib-copy
npm error command C:\windows\system32\cmd.exe /d /s /c husky install
npm error 'husky' is not recognized as an internal or external command

⇒ 安装直接失败(exit 1)。

加上 --ignore-scripts 后:

复制代码
$ npm install --ignore-scripts
added 1 package in 581ms        ← exit 0 ✓

3.2 但 README 里那条 .tgz 命令是好的

交付 README 写的安装方式是:

sh 复制代码
npm install /path/to/react-native-ratings-8.1.0.tgz

实测这条路径完全没问题:

检查 结果
npm install <pkg>.tgz exit 0 ✓,added 223 packages
入口可解析 ✓ node_modules/react-native-ratings/dist/index.js
lodash 是否自动装上 ✅ 装上了
图片资源 ✅ 6 张齐

原因 :prepare 不会因为 tarball 依赖而触发 ,而 tarball 里带着完整的 dependencies,npm 会正常装 lodash。

3.3 所以真正的结论(要精确)

路径 prepare lodash 结果
.tgz(README 写的) 不触发 自动装 ✅ 可用
目录 / file:(开发常用) 触发 ⇒ 失败 不会装 ❌ exit 1 ;加 --ignore-scripts 后能装,但要手动补 lodash

而交付 README 全文搜索 ignore-scripts / prepare / husky / lodash ⇒ 零命中。

⇒ README 里那条命令是对的 ,但走目录/git 接入会同时踩两个坑,而文档一个字都没提 。

另外:prepare: "husky install" 与 dependencies: {lodash} 都是上游自带的 (npm 元数据里同样存在)⇒ 是继承来的隐患 ,交付包既没处理、也没说明。

开发路径的规避写法:

bash 复制代码
npm install ../react-native-ratings --ignore-scripts
npm install lodash --ignore-scripts     # 必须补,目录依赖不会自动带上它

四、三处"看着像缺陷",查证后都不是

这三条如果只看交付包就下结论,就会把上游的问题记到交付方头上。先查 npm:

疑点 查证 结论
dist/images/images/ 重复嵌套目录 (airbnb-star*.png 等 6 张图出现两次) npm 上游包里同样存在 是上游 postbuild 的 cp -R ./src/images ./dist/images/ 跑重了 造成的构建副产物 ⇒ 不是交付方多提交
spec.json 缺 upstreamCommit 查 npm 元数据:gitHead 是空的 有客观原因 (上游 npm 里就没这个字段)⇒ 不算应修项
spec.json 的 upstream.url 用 git+ssh:// 与 npm 的 repository 字段完全一致 不是缺陷

⇒ 交付包实现与产物双重零改动 (src/** 与 dist/** 全部与上游逐字节一致,只差 package.json)。


五、★ 核心:React 19 下 AirbnbRating 的默认值全部失效

5.1 源码里的结构性差异

组件 导出名 组件类型 默认值写法
SwipeRating Rating class static defaultProps = {...}
TapRating AirbnbRating 函数组件 (而且是箭头函数 :const TapRating: React.FunctionComponent<...> = props => {...}) TapRating.defaultProps = {...}
Star (内部) 函数组件 Star.defaultProps = { starImage, selectedColor, unSelectedColor }

而 React 19 移除了函数组件的 defaultProps 支持 (class 的仍然支持)。宿主 React = 19.2.3。

5.2 实测:三区对照,数星星

测试页放了三个区,每颗星是一个 Image,默认 size = 40vp = 120px:

区 写法 实测 Image 节点数 结论
A <AirbnbRating />(完全不传 props) 0 ❌ 一颗星都没有
B <AirbnbRating count={5} starImage={require('react-native-ratings/dist/images/airbnb-star.png')} /> 5 × 120×120 ✓ 正常
C <Rating />(完全不传 props,class) 5 × 120×120 ✓✓ class 的 defaultProps 生效

5.3 根因链

复制代码
TapRating 是函数组件
  └─ React 19 忽略 TapRating.defaultProps
      └─ count === undefined
          └─ _.times(undefined, fn) === []        (lodash 把 undefined 当 0)
              └─ rating_array 为空
                  └─ 渲染 0 个 <Star>             ← 实测吻合(A 区 0 个 Image)

其余默认值(showRating、reviewColor、reviewSize)同样失效。

(defaultRating 被兜住了:useEffect 里发现它是 null/undefined 会 setPosition(3),所以内部位置仍是 3。)

5.4 规避写法:AirbnbRating 必须显式传参

tsx 复制代码
import {AirbnbRating} from 'react-native-ratings';

<AirbnbRating
  count={5}                    // ★ 必须传!函数组件 defaultProps 在 React 19 失效
  defaultRating={3}            // ★ 建议显式传
  reviews={['很差', '较差', '一般', '不错', '很好']}
  showRating
  reviewColor="rgba(230, 196, 46, 1)"
  reviewSize={25}
  size={40}
  starImage={require('react-native-ratings/dist/images/airbnb-star.png')}
/>

Rating(class)不需要额外处理 ------ 实测不传 props 也渲染 5 颗星 ✓

⚠️ starImage 那一条我标为"代码级成立、运行时未单独验证" :本轮 B 区是同时 显式传了 count 和 starImage,所以"只传 count、不传 starImage"这个情形没有单独测 。按源码看,Star.defaultProps.starImage 同样会被 React 19 忽略 ⇒ const starSource = fill && selectedColor === null ? STAR_SELECTED_IMAGE : starImage; 会得到 undefined ⇒ <Animated.Image source={undefined}>。所以建议一并显式传上最稳。

5.5 顺手确认的其他真值

  • 导出是 export { Rating, AirbnbRating } ⇒ 没有 default 导出,必须具名导入;
  • SwipeRating.defaultProps(class,React 19 生效 ,实测 9 键 ):type: 'star'、ratingImage、ratingColor: '#f1c40f'、ratingBackgroundColor: 'white'、ratingCount: 5 、showReadOnlyText: true、imageSize: 40 、minValue: 0、jumpValue: 0;
  • TapRating.defaultProps(函数,React 19 失效 ,实测 6 键 ):defaultRating: 3、reviews: ['Terrible','Bad','Okay','Good','Great']、count: 5 、showRating: true、reviewColor: 'rgba(230, 196, 46, 1)'、reviewSize: 25;
  • TapRating 是箭头函数 ⇒ typeof TapRating.prototype === 'undefined'(没有 prototype);
  • Star:TouchableOpacity + Animated.Image,尺寸 size || STAR_SIZE(40),tintColor 由 fill ? selectedColor : unSelectedColor 决定;点击做一次 Animated.spring(useNativeDriver: true)并调 starSelectedInPosition(position);
  • SwipeRating 用 PanResponder 做横向拖动评分;
  • 图片资源 6 张:airbnb-star、airbnb-star-selected、bell、heart、rocket、star ⇒ 打包时资源数 14 → 20(+6,精确对上) ✓。

六、接入方式

bash 复制代码
# ① 从适配仓库(.tgz 或 git)
npm install "git+https://atomgit.com/oh-react-native/react-native-ratings.git#8.1.0-ohos-1.0.0" --ignore-scripts

# ② 本地目录接入(开发常用)------ 记得补依赖
npm install ../react-native-ratings --ignore-scripts
npm install lodash --ignore-scripts
js 复制代码
// metro.config.js ------ file: / 软链装法需要把真实目录加进 watchFolders
watchFolders: [ path.resolve(__dirname, '../react-native-ratings') ],
js 复制代码
import RatingsTestApp from './RatingsTestApp';
AppRegistry.registerComponent('RatingsTestApp', () => RatingsTestApp);
bash 复制代码
hdc shell aa force-stop com.rnoh084.demo
hdc shell aa start -b com.rnoh084.demo -a EntryAbility --ps rnAppKey RatingsTestApp

不需要 HAR、权限、ohpm、link-harmony。


七、我自己的一个失误(写断言时踩的)

第一版断言里我写了:

复制代码
✗ [A] AirbnbRating 的 prototype 是空对象|期望 object|实际 undefined

错因 :TapRating 是箭头函数 (const TapRating = props => {...}),而箭头函数没有 prototype ⇒ 实测 undefined。

我按"普通函数/class 会有一个空的 prototype 对象"去写了。

纠正 :改成断言 typeof prototype === 'undefined',并把它当作**"这个导出是箭头函数"的证据**。修正后 15 / 15 全部通过。


八、已知限制与本次验证边界

8.1 用这个库要知道的

事项 说明
AirbnbRating 必须显式传 count 函数组件 defaultProps 在 React 19 失效 ⇒ 不传就一颗星都不渲染
starImage 建议一并显式传 Star 也是函数组件,它的 starImage 默认值同样失效
Rating 不需要额外处理 class 组件,static defaultProps 在 React 19 仍生效
有运行时依赖 lodash 目录/file: 接入不会自动装,要手动补
目录/file: 接入必须 --ignore-scripts 否则 prepare: "husky install" 让安装直接失败
没有 default 导出 必须 import { Rating, AirbnbRating } from 'react-native-ratings'
dist/images/images/ 是上游副产物 该库产物里有重复嵌套的图片目录,属上游问题,不影响使用

8.2 本次验证的边界

  1. 只在一台模拟器上验证 (Pura X View,density 3,1320×2232),没有真机。
  2. "只传 count、不传 starImage"未单独测 (见 5.4 的说明)⇒ 该结论属代码级成立、运行时未验证。
  3. 点击星星的交互路径未完整验证 :测试页装了 onFinishRating,但本轮驱动重点是"数星星",没有点星并回读回调值 ;onFinishRating / onStartRating / onSwipeRating 的触发与参数未验证。
  4. Rating 的 PanResponder 拖动评分未测 (jumpValue / minValue / fractions 等均未验证)。
  5. Rating 屏上"4 实心 + 1 空心"的显示状态没有对照源码公式推导(只记录了实测观感)。
  6. 自定义 size / selectedColor / unSelectedColor / starStyle / starContainerStyle / ratingContainerStyle 未测。
  7. reviewColor / reviewSize / showRating 的显示效果未逐项核对 (A 区因 count 已失效,更看不到 review 文本)。
  8. 没有跨平台对照(未在 iOS/Android 上跑同一套用例)。

未改动库代码。


九、常见问题

Q1:为什么 <AirbnbRating /> 什么都不显示?

因为它是函数组件 ,默认值写在 TapRating.defaultProps 上,而 React 19 已移除函数组件的 defaultProps 支持 ⇒ count 是 undefined ⇒ _.times(undefined, ...) 得到空数组 ⇒ 渲染 0 颗星。

解法 :显式传 count:

tsx 复制代码
<AirbnbRating count={5} defaultRating={3} />

Q2:为什么 <Rating /> 就正常?

因为 Rating(源码里的 SwipeRating)是 class 组件 ,用的是 static defaultProps ------ React 19 对 class 的 defaultProps 仍然支持 ⇒ 不传 props 也能拿到 ratingCount: 5、imageSize: 40 等默认值。

⇒ 同一个库两个组件表现不同,判断时必须分开看。

Q3:npm install 报 'husky' is not recognized as an internal or external command?

因为包里有 "prepare": "husky install",而 husky 是 devDependency(消费者装不到)。prepare 在目录依赖 / git 依赖时会触发 ⇒ 安装失败。

解法:

bash 复制代码
npm install <path-or-git> --ignore-scripts

(走 .tgz 安装不会触发 prepare,所以按 README 那条命令装是没问题的。)

Q4:Unable to resolve module lodash?

这个库真的依赖 lodash (_.times、_.map)。用目录/file: 接入时,npm 不会 安装目标包自己的 dependencies,所以要手动补:

bash 复制代码
npm install lodash --ignore-scripts

(用 .tgz 安装则会被自动带上。)

Q5:星星的图片能换吗?

可以,AirbnbRating 传 starImage(图片源)即可;Rating 传 ratingImage,并且支持 type: 'star' | 'heart' | 'rocket' | 'bell' | 'custom':

tsx 复制代码
<Rating type="heart" ratingCount={5} imageSize={40} />

Q6:为什么我要显式传 starImage?

因为 Star 也是函数组件,它的 starImage 默认值在 React 19 下同样失效 ⇒ 不传就可能拿到 source={undefined}。显式传最稳(这一条属代码级推断,见 5.4)。

Q7:import Rating from 'react-native-ratings' 为什么拿不到东西?

因为本库没有 default 导出,只有具名导出:

tsx 复制代码
import {Rating, AirbnbRating} from 'react-native-ratings';   // ✅

Q8:dist/images/images/ 这个重复目录是什么?

是上游构建脚本 postbuild: "yarn run copy-files"(cp -R ./src/images ./dist/images/)跑重造成的副产物 ,npm 官方包里同样存在,不影响使用。

Q9:想改默认星星数量/尺寸怎么办?

  • Rating:ratingCount、imageSize、ratingColor、ratingBackgroundColor、minValue、jumpValue;
  • AirbnbRating:count、size、defaultRating、reviews、showRating、reviewColor、reviewSize、selectedColor。

⚠️ 对 AirbnbRating 而言,所有默认值都不要指望(React 19)------ 想用什么就显式传什么。

Q10:这个库需要鸿蒙侧做什么特殊处理吗?

不需要。 纯 JS、无原生模块、不用 vector-icons、不需要 HAR / 权限 / ohpm。唯一要注意的就是本文说的默认值失效(React 19)与安装路径差异。


小结

react-native-ratings 是纯 JS 组件库,接入不需要任何鸿蒙侧改动,交付包实现与产物双重零改动 (38/39 文件与上游逐字节一致)。设备侧 15 / 15 断言通过。

最值得记下来的是这一条:

  1. 同一个库里,组件类型不同 ⇒ 默认值在 React 19 下的命运完全不同。

    Rating 是 class (static defaultProps)⇒ 不传 props 也渲染 5 颗星 ;AirbnbRating 是函数组件 ⇒ React 19 已移除函数组件的 defaultProps 支持 ⇒ 不传 props 渲染 0 颗星 。

    ⇒ 适配前先逐个组件看它是 class 还是函数,不要把这个库当成一个整体下结论。这条对任何"同时含 class 与函数组件"的老库都成立。

  2. 测安装要把"文档写的路径"和"开发常用路径"都跑一遍。

    同一个 prepare: "husky install":走 .tgz 不触发、安装成功 ;走目录/file: 直接 exit 1 失败 。lodash 也是:.tgz 自动装、file: 不装。

    ⇒ 只测一条路径就写"这个包能装/不能装",很容易写错。

  3. "看着像缺陷"的一定要先查 npm 再定性。

    这次三处疑点------重复的 dist/images/images/、缺 upstreamCommit、git+ssh:// 的仓库地址------查完都不是交付方的问题 (上游副产物 / npm gitHead 本就是空 / 与 npm repository 一致)。

  4. 箭头函数没有 prototype。

    const C = props => {} ⇒ typeof C.prototype === 'undefined';而 function C(){} 有(空对象)。我按后者写断言 ⇒ 假失败一条。⇒ 判断"是不是函数组件"要用 typeof,别假设 prototype 是对象。


本篇用到的库

项 内容
三方库 react-native-ratings(上游 8.1.0 的鸿蒙适配版)
适配仓库 https://atomgit.com/oh-react-native/react-native-ratings
适配 TAG 8.1.0-ohos-1.0.0
需要 HAR / 权限 / ohpm 都不需要(纯 JS)
库类型 纯 JS 组件库 :评分组件(滑动评分 Rating + 点击评分 AirbnbRating)
运行时依赖 ⚠️ lodash@^4.17.15 ⇒ 目录/file: 接入必须显式补装
prepare ⚠️ 有 "prepare": "husky install" ⇒ 目录/file: 接入必须加 --ignore-scripts
许可证 MIT
上游仓库 https://github.com/Monte9/react-native-ratings
宿主工程 RNOH084Demo(测试页 rnAppKey = RatingsTestApp)
bash 复制代码
# 从适配仓库安装
npm install "git+https://atomgit.com/oh-react-native/react-native-ratings.git#8.1.0-ohos-1.0.0" --ignore-scripts

# 本地目录接入(开发常用):必须忽略脚本 + 补 lodash
npm install ../react-native-ratings --ignore-scripts
npm install lodash --ignore-scripts
js 复制代码
// metro.config.js
watchFolders: [ path.resolve(__dirname, '../react-native-ratings') ],
tsx 复制代码
// 用法
import {Rating, AirbnbRating} from 'react-native-ratings';   // 注意:没有 default 导出

// ① 点击评分:★ React 19 下必须显式传 count(函数组件 defaultProps 失效)
<AirbnbRating
  count={5}
  defaultRating={3}
  reviews={['很差', '较差', '一般', '不错', '很好']}
  showRating
  size={40}
  starImage={require('react-native-ratings/dist/images/airbnb-star.png')}
  onFinishRating={(v) => console.log('评分', v)}
/>

// ② 滑动评分:class 组件,默认值在 React 19 下仍然生效
<Rating
  type="star"                        // star | heart | rocket | bell | custom
  ratingCount={5}
  imageSize={40}
  startingValue={3}
  onFinishRating={(v) => console.log('评分', v)}
/>
bash 复制代码
# 换页启动测试页(force-stop 不能省,换页参数只在冷启动生效)
hdc shell aa force-stop com.rnoh084.demo
hdc shell aa start -b com.rnoh084.demo -a EntryAbility --ps rnAppKey RatingsTestApp

验证环境

项 版本
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
宿主 HAP 产物 entry-default-signed.hap
本次构建 assembleHap 4 分 01 秒;改断言后重编 3 分 58 秒
验证规模 设备侧 15 / 15 断言;三区星数对照(0 / 5 / 5);安装路径 3 组 实验(目录不加/加 --ignore-scripts、.tgz);上游 39 个文件逐字节比对;打包资源 14→20

欢迎加入 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

相关推荐
朱包林1 小时前
iTerm2终端配置指南
linux·javascript·python·云原生·容器·kubernetes·云计算
谢亮_vipxieliang2 小时前
JavaScript this 指向完全指南:调用位置决定一切
开发语言·javascript·ecmascript
大雷神2 小时前
用鸿蒙开发助手推进《3D种植》鸿蒙版与 Flame 库适配
3d·华为·harmonyos
WeiXin_DZbishe2 小时前
基于Node.js的马拉松赛事管理系统的设计与实现-计算机毕设 附源码【课程设计】90211
java·javascript·vue.js·python·php·课程设计
轻口味2 小时前
HarmonyOS 7 新特性7:闪控窗深潜——六态流转、错误码二维查表与730ms双通道滞后真机实测
华为·harmonyos·鸿蒙·floatview
西西学代码2 小时前
像素和颜色之间的转换(Flutter)
前端·javascript·flutter
程序猿追2 小时前
从 24 到 25 字节:compactstr4cj 的 CJMP 鸿蒙适配与 UTF-8 实测
华为·harmonyos
呆萌很2 小时前
长尾分类(Long-tailed Classification)方法汇总
人工智能·分类
HarmonyOS_SDK3 小时前
【HarmonyOS API 26 】数字盾服务2.0:硬件级安全的随身虚拟U盾
harmonyos