React Native for OpenHarmony 实战:三方库 react-native-star-rating-widget 的鸿蒙化适配指南
本文记录把 react-native-star-rating-widget(星级评分 / 评分展示)适配到 HarmonyOS 的完整过程。
它做的事:用 SVG 画星星(react-native-svg 的 Path + LinearGradient),支持整星/半星/四分之一星、
PanResponder 拖动评分、Animated 点击缩放动画,另外提供一个只读的 StarRatingDisplay。
结论:可用,而且这是又一次"零 FAIL"—— 14 条判据全 PASS。
- 预检的老写法九项 +
ListView+ViewPropTypes+Text.propTypes+this.refs+mixins+componentWillReceiveProps+componentWillMount+getDefaultProps+Platform.OS+measure+NativeModules+<Modal—— 全部为空; createClass精确命中 0、裸词也 0;useNativeDriver3 处、都是true(驱动的是Animated.Value,正确用法);- 三个组件全是函数组件(
typeof是 function、prototype是undefined)—— 这次按真实形态断言,一次全过; - 核心判据是纯函数
getStars()的 8 条:整星 / 半星 / 四分之一星 / 不传 step 时不出半星 / 0 分 / 满分 /maxStars可变 —— 全部命中。
★★★★★ 一、预检:干净,但需要 react-native-svg 的 5 个符号
| 预检项 | 结果 |
|---|---|
老写法九项(createClass / React.PropTypes / ViewPropTypes / Text.propTypes / 字符串 ref / this.refs / mixins / componentWillReceiveProps / componentWillMount / getDefaultProps) | ✅✅ 全部为空 |
ListView.DataSource / <ListView | ✅ 空 |
measure() / NativeModules / <Modal | ✅ 全部空 |
Platform.OS | ✅ 空(平台差异交给 I18nManager) |
createClass | ✅ 精确 0、裸词 0 |
useNativeDriver | ✅ 3 处,都是 true |
onLayout | 3 处(量容器宽度,用于把手指位置换算成评分) |
Animated | 10 处(点击/拖动动画) |
dependencies | ✅ 空 |
peerDependencies | ⚠️ react / react-native / react-native-svg |
| 外部 import | react / react-native / react-native-svg |
react-native-svg 的 5 个符号 —— 但现有桩能直接兜住
源码(src/StarIcon.tsx L3):
import Svg, { Path, Defs, LinearGradient, Stop } from 'react-native-svg';
⇒ 需要 Svg(默认)+ Path / Defs / LinearGradient / Stop 共 5 个。
而我工程里已有的 svg 桩是用 Proxy 兜底的:
const handler = {
get: (_target, prop) => {
if (prop === '__esModule') return true;
if (prop === 'default') return makeStub('default');
if (typeof prop === 'symbol') return undefined;
return makeStub(String(prop)); // ← 任何具名导出都返回一个 View 桩
},
};
module.exports = new Proxy({}, handler);
⇒ ⇒ 所以 Path / Defs / LinearGradient / Stop 全都能拿到(都是 View 桩),不需要改桩 ✓✓
这条经验值得记:用 Proxy 兜底的桩,对"图标/图形库"这类"符号很多但你只用到几个"的依赖特别省事 ——
换成"逐个列出具名导出"的写法,每遇到一个新库就要补一次。
★★★★★ 二、三个组件全是函数组件(这次按真实形态断言,一次全过)
先看源码(坑 336 的固定动作):
src/StarRating.tsx L174: const StarRating = ({...}) => {
src/StarIcon.tsx L125: const StarIcon = ({...}) => {
src/StarRatingDisplay.tsx L75: const StarRatingDisplay = ({...}) => {
⇒ 三个都是 const X = ({...}) => {…}(函数组件)
实测 9 条全 PASS:
NOTE | StarRating 的 typeof = function
PASS | ★★★ StarRating 是 function
NOTE | StarRating.prototype 的 typeof = undefined
PASS | ★★★ StarRating 没有 prototype(它是函数组件,不是 class)
NOTE | StarIcon 的 typeof = function
PASS | ★★★ StarIcon 是 function
NOTE | StarIcon.prototype 的 typeof = undefined
PASS | ★★★ StarIcon 没有 prototype(它是函数组件,不是 class)
NOTE | StarRatingDisplay 的 typeof = function
PASS | ★★★ StarRatingDisplay 是 function
NOTE | StarRatingDisplay.prototype 的 typeof = undefined
PASS | ★★★ StarRatingDisplay 没有 prototype(它是函数组件,不是 class)
PASS | ★★ 入口默认导出非 null
PASS | ★★ 具名 StarIcon 非 null
PASS | ★★ 具名 StarRatingDisplay 非 null
三档判别表(已在三轮里全部实测过):
| 写法 | typeof | prototype | prototype.render | 实测出处 |
|---|---|---|---|---|
class X extends Component | function | object | function | 某个表格库的 Table |
const X = ({…}) => … | function | undefined | undefined | 本轮三个组件、某个向导库 |
X = React.forwardRef(…) | object | undefined | undefined | 某个下拉选择库、某个图片查看库 |
⇒ ⇒ 固定动作:先 grep 'export default' 看那一行,再选对应断言 ✓
★★★★★ 三、核心判据:getStars() 纯函数的 8 条(零设备依赖)
src/utils.ts:
export function getStars(rating: number, maxStars: number, step?: 'half' | 'quarter' | 'full') {
return [...Array(maxStars)].map((_, i) => {
const remainder = rating - i;
if (remainder >= 1) return 'full';
if (step === 'quarter') {
if (remainder >= 0.75) return 'three-quarter';
if (remainder >= 0.5) return 'half';
if (remainder >= 0.25) return 'quarter';
} else if (step === 'half') {
if (remainder >= 0.5) return 'half';
}
return 'empty';
});
}
我按这个逻辑手推出 8 组期望值,实测全部命中:
| # | 调用 | 我推出的期望 | 实测 |
|---|---|---|---|
| 1 | getStars(3, 5) | ["full","full","full","empty","empty"] | ✅ |
| 2 | getStars(3.5, 5, 'half') | ["full","full","full","half","empty"] | ✅ |
| 3 | getStars(3.5, 5)(不传 step) | ["full","full","full","empty","empty"] | ✅ |
| 4 | getStars(2.75, 5, 'quarter') | ["full","full","three-quarter","empty","empty"] | ✅ |
| 5 | getStars(1.25, 5, 'quarter') | ["full","quarter","empty","empty","empty"] | ✅ |
| 6 | getStars(0, 5) | 5 个 empty | ✅ |
| 7 | getStars(5, 5) | 5 个 full | ✅ |
| 8 | getStars(2, 3) 长度 | 3 | ✅ |
第 3 条最有价值:
NOTE | getStars(3.5, 5)(不传 step) = ["full","full","full","empty","empty"]
PASS | ★★★ 不传 step ⇒ [full,full,full,empty,empty](第 4 颗不会变成 half)
⇒ ⇒ 同样是 3.5 分,step='half' 时第 4 颗是 half、不传 step 时第 4 颗是 empty ✓✓
⇒ 这说明 step 不是"自动推断"的,必须显式传 —— 一个"读代码容易忽略、但实际会影响显示"的细节 ✓
第 4 条也很关键:2.75 + step='quarter' ⇒ 第 3 颗是 three-quarter
(因为 remainder = 0.75 ⇒ >= 0.75 命中第一档)✓
★★ 四、精确默认值(从 .d.ts 的 @default 注释 + 源码读)
.d.ts 的注释里直接写了默认值:
* @default '#fdd835' ← color
* @default color ← emptyColor(默认跟随 color)
* @default 5 ← maxStars
* @default 32 ← starSize
源码里也能对上:
// src/StarRating.tsx L166
const defaultColor = '#fdd835';
// src/StarIcon.tsx L12
const RTL_TRANSFORM: ViewStyle = { transform: [{ rotateY: '180deg' }] };
⇒ color='#fdd835'(Material 黄)、maxStars=5、starSize=32 ✓
★★★ 五、StarIcon 的 5 种类型
源码的类型定义(src/StarIcon.tsx L9):
type: 'full' | 'half' | 'quarter' | 'three-quarter' | 'empty';
⇒ 恰好 5 种,与 getStars() 的返回值一一对应 ✓
渲染上它们是这样实现的(StarIcon.tsx L16-110):
StarBorder—— 星星轮廓(Path描边)StarFull—— 完整星星:Defs+LinearGradient+Path(渐变填充)StarQuarter/StarThreeQuarter/StarHalf—— 用不同 offset 的LinearGradient实现"部分填充"
⇒ 所以这个库对 react-native-svg 的依赖是"真实功能性的"(不是可选美化)——
用桩隔离时会看到"星星变成空白方块/View",布局位置正确但图形不显示。
这一点要在结论里说清楚:组件结构可用,但"星星长什么样"未经真实 SVG 验证。
★ 六、其余预检结论
| 预检项 | 结果 |
|---|---|
| 交付包 | lib/commonjs(4 文件)+ lib/module + lib/typescript(全套 .d.ts) |
| 入口导出 | lib/commonjs/index.js(800 B)有 3 个:default(StarRating)/ StarIcon / StarRatingDisplay |
StarRating 的实现 | PanResponder 拖动 + Animated 缩放 + onLayout 量宽 + AccessibilityInfo 无障碍播报 |
AccessibilityInfo | 实现了 increment / decrement / activate 三种无障碍动作 |
| RTL 支持 | I18nManager + rotateY: '180deg' |
getStars 不在入口导出 | 要走 lib/commonjs/utils 子路径 |




七、接入与预检(要点)
npm install "git+https://atomgit.com/oh-react-native/react-native-star-rating-widget.git#1.11.0-ohos-1.0.0" --ignore-scripts
# ★ peer:react-native-svg(真功能依赖)⇒ 装官方鸿蒙版,或用 Proxy 兜底的桩隔离
八、交付包质量与基线说明
| 类别 | 结果 |
|---|---|
| 交付包内容 | lib/commonjs(index / StarRating / StarIcon / StarRatingDisplay / utils)+ lib/module + lib/typescript ✓ |
spec.json | ⚠️ 缺 upstreamCommit |
validation | ⚠️ "build-pass-device-pending" |
| ⚠️ 声明的基线 | reactNative: "0.88.0" + rnoh: "0.84.4" ⇒ 两字段自相矛盾 |
| 备注 | 库代码零改动 |
关于基线:本次验证在 RN 0.84.1 + RNOH 0.84.3 上完成。
本库零平台分支(Platform.OS 零命中)、无原生代码 ⇒ 逻辑结论跨基线成立;
⚠️ 但**"星星图形本身"依赖 react-native-svg**,本轮用桩隔离 ⇒ 图形的真实渲染未验证。
九、已知限制与本次验证边界
9.1 用这个库要知道的
| 事项 | 说明 |
|---|---|
| ★★★★★ 三个组件全是函数组件 | typeof 是 function、prototype 是 undefined |
★★★★★ step 必须显式传 | 同样是 3.5 分:step='half' ⇒ 第 4 颗 half;不传 ⇒ 第 4 颗 empty |
★★★★★ getStars 是纯函数 | 8 条判据全部可离线验证(整星/半星/四分之一/0/满分/maxStars 可变) |
★★★★ react-native-svg 是真功能依赖 | 用了 Svg / Path / Defs / LinearGradient / Stop 5 个符号;不是可选美化 |
| ★★★★ 默认值 | color='#fdd835' / maxStars=5 / starSize=32 / emptyColor 默认跟随 color |
| ★★★ 5 种星星类型 | 'full' | 'half' | 'quarter' | 'three-quarter' | 'empty' |
| ★★★ 入口 3 个导出 | 默认 StarRating + 具名 StarIcon / StarRatingDisplay |
★★ getStars 不在入口 | 要走 react-native-star-rating-widget/lib/commonjs/utils |
| ★★ 支持无障碍 | accessibilityRole="adjustable" + increment/decrement/activate 三种动作 |
| ★★ RTL 支持 | I18nManager + rotateY: '180deg' |
9.2 本次验证的边界
- ★★★ 星星图形本身未验证(
react-native-svg用桩隔离 ⇒ 星星不显示图形,只保留布局): - ★★★
accessibilityLabel里的%value%替换未在布局 dump 里观察到(含 “STB” 的节点数为 0); - ★★★ 未验证
PanResponder拖动评分(onChange未被真实触摸触发); - ★★ 未验证
Animated点击缩放动画(useNativeDriver: true的那两条); - ★★ 未验证
StarRatingDisplay与StarRating的视觉差异(桩下都是空白); - 未验证
enableSwiping/animationConfig/onRatingStart/onRatingEnd; - 未验证 RTL(
I18nManager强制 RTL)下的rotateY效果; - 未在
RN 0.88.0/RNOH 0.84.4上复验; - 未测其它屏幕宽度;未做跨平台对照。
库代码未改动(用的是工程里已有的 stubs/react-native-svg.js,本轮没有新增/修改桩)。
十、常见问题
Q1:它在 RN 0.84 上能跑吗?
逻辑层能跑,图形层未验证。
14 条判据全 PASS:三个组件都是函数组件、入口 3 个导出都在、getStars 8 条全中;LogBox 无通知。
但星星是 SVG 画的,本轮把 react-native-svg 用 Proxy 桩隔离 ⇒ 只保证"结构/布局",不保证"图形"。
Q2:step 到底要不要传?
要显式传。同样是 3.5 分:
getStars(3.5, 5, 'half'); // ['full','full','full','half','empty'] ← 第 4 颗是半星
getStars(3.5, 5); // ['full','full','full','empty','empty'] ← 第 4 颗是空星
⇒ 不传 step 时不会自动推断出半星 —— 这是实测确认的(不是文档推测)。
Q3:getStars 怎么 import?
它不在入口导出,要走子路径:
import {getStars} from 'react-native-star-rating-widget/lib/commonjs/utils';
getStars(4.5, 5, 'half'); // ['full','full','full','full','half']
Q4:默认值是多少?
color = '#fdd835'(Material 黄)、maxStars = 5、starSize = 32、emptyColor 默认跟随 color。
(.d.ts 的 @default 注释与源码常量 defaultColor 一致)
Q5:为什么用 svg 桩也能跑?
因为工程里的 svg 桩是 Proxy 兜底的 —— 任何具名导出都返回一个 View 桩:
module.exports = new Proxy({}, {
get: (_t, prop) => prop === '__esModule' ? true : makeStub(String(prop)),
});
⇒ Path / Defs / LinearGradient / Stop 全部拿得到 ⇒ 模块图能解析、组件能渲染(只是图形不显示)。
Q6:StarIcon 有哪些 type?
恰好 5 种:'full' | 'half' | 'quarter' | 'three-quarter' | 'empty'
—— 与 getStars() 的返回值一一对应。所以 getStars 的输出可以直接喂给 StarIcon。
Q7:只显示不可交互怎么办?
用 StarRatingDisplay(它不接 PanResponder):
import {StarRatingDisplay} from 'react-native-star-rating-widget';
<StarRatingDisplay rating={3.5} starSize={28} step="half" />
小结
react-native-star-rating-widget 是星级评分组件(SVG 画星 + PanResponder 拖动 + Animated 缩放),
另提供只读的 StarRatingDisplay。预检很干净、14 条判据零 FAIL。
最值得记下来的是这几条:
-
★★★★★
getStars()是纯函数,8 条判据全 PASS —— 而且期望值全是手推的。
整星 / 半星 / 四分之一 / 0 分 / 满分 /maxStars可变,全部命中 ✓
⇒ 其中最有价值的一条:同样是3.5分,step='half'时第 4 颗是half,不传step时是empty
⇒step不会自动推断 —— 这是"读代码容易漏、实际影响显示"的细节,靠实测才钉死 ✓ -
★★★★★
Proxy兜底的 svg 桩,对"符号多但只用几个"的依赖特别省事。
这个库要Svg/Path/Defs/LinearGradient/Stop5 个符号,
而工程里那份桩用Proxy把任何具名导出都变成View桩 ⇒ 一行都不用改 ✓
⇒ 如果桩是"逐个列举具名导出"的写法,每遇一个新库就要补一次。 -
★★★★★ 三个组件全是函数组件,按
prototype === undefined断言 ⇒ 9 条全过。
⇒ 至此三档判别表已在三轮里全部实测:
class ⇒prototype.render是 function;函数组件 ⇒prototype是undefined;forwardRef⇒typeof是object。 -
★★★★ 诚实区分"结构可用"与"图形未验证"。
星星是 SVG 画的,本轮用桩隔离 ⇒ 布局/结构/逻辑已验证,图形渲染未验证。
⇒ 这类"视觉强相关"的库,结论必须分层写,否则读者会误以为"星星能正常显示"。 -
★★★
step这类"看起来有默认行为"的参数,实测一下再下结论。
我原本以为3.5分会自动显示半星 —— 实测不传step就是空星 ✗
⇒ “组件的名字里有 half” ≠ “它默认就是半星精度” ✓ -
★★
accessibilityLabel的%value%替换没能在 dump 里看到。
源码里是accessibilityLabel.replace(/%value%/g, stagedRating),逻辑上成立,
但布局 dump 里含 “STB” 的节点数为 0 ⇒ 如实记"未观察到",不当成结论 ✓
本篇用到的库
| 项 | 内容 |
|---|---|
| 三方库 | react-native-star-rating-widget(上游 1.11.0 的鸿蒙适配版) |
| 适配仓库 | https://atomgit.com/oh-react-native/react-native-star-rating-widget |
| 适配 TAG | 1.11.0-ohos-1.0.0 |
| 需要 HAR / 权限 / ohpm | 都不需要(纯 JS,但依赖 react-native-svg) |
| 库类型 | 星级评分(SVG 画星 + 拖动 + 缩放动画)+ 只读展示 |
| 运行时依赖 | ✅ dependencies 空;peer 有 react-native-svg(真功能依赖) |
| 导出形态 | 默认 StarRating + 具名 StarIcon / StarRatingDisplay;三个都是函数组件 |
| 核心纯函数 | getStars(rating, maxStars, step?)(在 lib/commonjs/utils,入口未导出) |
| 许可证 | MIT |
| 上游仓库 | https://github.com/bviebahn/react-native-star-rating-widget |
| 宿主工程 | RNOH084Demo(测试页 rnAppKey = StarRatingTestApp) |
npm install "git+https://atomgit.com/oh-react-native/react-native-star-rating-widget.git#1.11.0-ohos-1.0.0" --ignore-scripts
# peer react-native-svg 是真功能依赖 ⇒ 装官方鸿蒙版;隔离验证可用 Proxy 兜底的桩
import StarRating, {StarRatingDisplay, StarIcon} from 'react-native-star-rating-widget';
import {getStars} from 'react-native-star-rating-widget/lib/commonjs/utils';
// ① 可交互评分
<StarRating
rating={rating}
onChange={(r) => setRating(r)}
maxStars={5}
starSize={32}
step="half" // ★ 不传就不会出半星
color="#fdd835"
/>
// ② 只读展示
<StarRatingDisplay rating={3.5} starSize={28} step="half" />
// ③ 单独用星星(5 种 type)
<StarIcon index={0} type="half" size={32} color="#fdd835" />
// ④ 纯函数:把评分换算成星星类型数组
getStars(3.5, 5, 'half'); // ['full','full','full','half','empty']
getStars(3.5, 5); // ['full','full','full','empty','empty'] ← ★ 不传 step
getStars(2.75, 5, 'quarter');// ['full','full','three-quarter','empty','empty']
// ⚠️ 三个组件都是函数组件 ⇒ typeof 是 'function'、prototype 是 undefined
// ⚠️ step 必须显式传(不传 ⇒ 只出整星/空星,不出半星)
// ⚠️ react-native-svg 是真功能依赖(Svg/Path/Defs/LinearGradient/Stop 5 个符号)
// ⚠️ 默认值:color='#fdd835' / maxStars=5 / starSize=32
// ⚠️ StarIcon 的 type 恰好 5 种:full / half / quarter / three-quarter / empty
# 换页启动测试页 —— force-stop 不能省
hdc shell aa force-stop com.rnoh084.demo
hdc shell aa start -b com.rnoh084.demo -a EntryAbility --ps rnAppKey StarRatingTestApp
判据的复现方式
# ① 契约 + getStars 8 条(14 条全 PASS)
hdc shell "hilog -x | grep srw-test" | Select-String 'PASS|FAIL|SUMMARY|NOTE'
# ⇒ 三个组件都是 function 且 prototype 是 undefined
# ⇒ getStars 8 条全部命中(含"不传 step ⇒ 不出 half")
环境搭建
本手册的验证环境是 React Native for OpenHarmony(RNOH)。从零准备开发环境(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 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 |
| 本机状态栏高度 | 117px = 39vp |
| 宿主依赖 | ✅ 复用已有 stubs/react-native-svg.js(Proxy 兜底),本轮未改桩 |
| 宿主 HAP 产物 | entry-default-signed.hap |
| 本次构建 | assembleHap 3 分 50 秒 ⇒ 一次成功(打包与构建退出码都检查了) |
| 验证规模 | 14/14 全 PASS、零 FAIL(契约 9 + getStars 8 中重叠计);三个组件都是函数组件;getStars 8 条全中;LogBox 无通知;4 张截图互异;⚠️ 星星图形未验证 |
⚠️ 基线说明:交付包
spec.json声明RN 0.88.0 + RNOH 0.84.4(两字段自相矛盾),本次验证在RN 0.84.1 + RNOH 0.84.3上完成。
逻辑结论跨基线成立;图形渲染依赖react-native-svg,本轮用桩隔离 ⇒ 未验证。
相关社区与项目地址
| 项目 | 地址 |
|---|---|
| React Native 鸿蒙官方社区(CPF-RN) | https://atomgit.com/CPF-RN |
| RNOH 框架仓库 | https://atomgit.com/openharmony-RN/ohos_react_native |
| RNOH 三方库使用文档 | https://atomgit.com/OpenHarmony-RN/usage-docs |
| React Native for OpenHarmony 组织 | https://atomgit.com/oh-react-native |
| RN 三方库鸿蒙适配清单 | https://atomgit.com/oh-react-native/rn-ohos-adaptation-overview |
欢迎加入 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
更多推荐



所有评论(0)