本文记录把 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 不是空的:

"dependencies": { "lodash": "^4.17.15" }

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


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

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

package.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 写的安装方式是:

npm install /path/to/react-native-ratings-8.1.0.tgz

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

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

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

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

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

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

⇒ README 里那条命令是对的,但走目录/git 接入会同时踩两个坑,而文档一个字都没提。
另外:prepare: "husky install" 与 dependencies: {lodash} 都是上游自带的(npm 元数据里同样存在)⇒ 是继承来的隐患,交付包既没处理、也没说明。

开发路径的规避写法:

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 源码里的结构性差异

组件导出名组件类型默认值写法
SwipeRatingRatingclassstatic defaultProps = {...}
TapRatingAirbnbRating函数组件(而且是箭头函数: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 必须显式传参

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,精确对上) ✓。

六、接入方式

# ① 从适配仓库(.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
// metro.config.js —— file: / 软链装法需要把真实目录加进 watchFolders
watchFolders: [ path.resolve(__dirname, '../react-native-ratings') ],
import RatingsTestApp from './RatingsTestApp';
AppRegistry.registerComponent('RatingsTestApp', () => RatingsTestApp);
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:

<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 依赖时会触发 ⇒ 安装失败。

解法:

npm install <path-or-git> --ignore-scripts

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

Q4:Unable to resolve module lodash?

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

npm install lodash --ignore-scripts

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

Q5:星星的图片能换吗?

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

<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 导出,只有具名导出:

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
适配 TAG8.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)
# 从适配仓库安装
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
// metro.config.js
watchFolders: [ path.resolve(__dirname, '../react-native-ratings') ],
// 用法
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)}
/>
# 换页启动测试页(force-stop 不能省,换页参数只在冷启动生效)
hdc shell aa force-stop com.rnoh084.demo
hdc shell aa start -b com.rnoh084.demo -a EntryAbility --ps rnAppKey RatingsTestApp

验证环境

项版本
React Native0.84.1
React19.2.3
RNOH(npm / ohpm)@react-native-oh/react-native-harmony / @rnoh/react-native-openharmony 0.84.3
Node.js / npmv24.14.0 / 11.9.0
DevEco Studio26.0.0.621
HarmonyOS SDKAPI 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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐