本文记录把 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;
  • useNativeDriver 3 处、都是 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
onLayout3 处(量容器宽度,用于把手指位置换算成评分)
Animated10 处(点击/拖动动画)
dependencies✅ 空
peerDependencies⚠️ react / react-native / react-native-svg
外部 importreact / 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

三档判别表(已在三轮里全部实测过):

写法typeofprototypeprototype.render实测出处
class X extends Componentfunctionobjectfunction某个表格库的 Table
const X = ({…}) => …functionundefinedundefined本轮三个组件、某个向导库
X = React.forwardRef(…)objectundefinedundefined某个下拉选择库、某个图片查看库

⇒ ⇒ 固定动作:先 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 组期望值,实测全部命中:

#调用我推出的期望实测
1getStars(3, 5)["full","full","full","empty","empty"]✅
2getStars(3.5, 5, 'half')["full","full","full","half","empty"]✅
3getStars(3.5, 5)(不传 step)["full","full","full","empty","empty"]✅
4getStars(2.75, 5, 'quarter')["full","full","three-quarter","empty","empty"]✅
5getStars(1.25, 5, 'quarter')["full","quarter","empty","empty","empty"]✅
6getStars(0, 5)5 个 empty✅
7getStars(5, 5)5 个 full✅
8getStars(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 本次验证的边界

  1. ★★★ 星星图形本身未验证(react-native-svg 用桩隔离 ⇒ 星星不显示图形,只保留布局):
  2. ★★★ accessibilityLabel 里的 %value% 替换未在布局 dump 里观察到(含 “STB” 的节点数为 0);
  3. ★★★ 未验证 PanResponder 拖动评分(onChange 未被真实触摸触发);
  4. ★★ 未验证 Animated 点击缩放动画(useNativeDriver: true 的那两条);
  5. ★★ 未验证 StarRatingDisplay 与 StarRating 的视觉差异(桩下都是空白);
  6. 未验证 enableSwiping / animationConfig / onRatingStart / onRatingEnd;
  7. 未验证 RTL(I18nManager 强制 RTL)下的 rotateY 效果;
  8. 未在 RN 0.88.0 / RNOH 0.84.4 上复验;
  9. 未测其它屏幕宽度;未做跨平台对照。

库代码未改动(用的是工程里已有的 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。

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

  1. ★★★★★ getStars() 是纯函数,8 条判据全 PASS —— 而且期望值全是手推的。
    整星 / 半星 / 四分之一 / 0 分 / 满分 / maxStars 可变,全部命中 ✓
    ⇒ 其中最有价值的一条:同样是 3.5 分,step='half' 时第 4 颗是 half,不传 step 时是 empty
    ⇒ step 不会自动推断 —— 这是"读代码容易漏、实际影响显示"的细节,靠实测才钉死 ✓

  2. ★★★★★ Proxy 兜底的 svg 桩,对"符号多但只用几个"的依赖特别省事。
    这个库要 Svg / Path / Defs / LinearGradient / Stop 5 个符号,
    而工程里那份桩用 Proxy 把任何具名导出都变成 View 桩 ⇒ 一行都不用改 ✓
    ⇒ 如果桩是"逐个列举具名导出"的写法,每遇一个新库就要补一次。

  3. ★★★★★ 三个组件全是函数组件,按 prototype === undefined 断言 ⇒ 9 条全过。
    ⇒ 至此三档判别表已在三轮里全部实测:
    class ⇒ prototype.render 是 function;函数组件 ⇒ prototype 是 undefined;forwardRef ⇒ typeof 是 object。

  4. ★★★★ 诚实区分"结构可用"与"图形未验证"。
    星星是 SVG 画的,本轮用桩隔离 ⇒ 布局/结构/逻辑已验证,图形渲染未验证。
    ⇒ 这类"视觉强相关"的库,结论必须分层写,否则读者会误以为"星星能正常显示"。

  5. ★★★ step 这类"看起来有默认行为"的参数,实测一下再下结论。
    我原本以为 3.5 分会自动显示半星 —— 实测不传 step 就是空星 ✗
    ⇒ “组件的名字里有 half” ≠ “它默认就是半星精度” ✓

  6. ★★ 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
适配 TAG1.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 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
本机状态栏高度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

Logo

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

更多推荐