本文记录把 react-native-simple-radio-button(简单单选按钮组)适配到 HarmonyOS 的完整过程。

它做的事很直接:一组单选按钮(一个圆点 + 一行文字标签),点哪一项哪一项被选中,并把选中结果回调出去;还支持多选问卷场景下的 clearSelection()(清空选择)。

结论:这属于"最省事"的一类库 —— 纯 JS、零依赖、没有一处已移除的 API,而且 RadioForm / RadioButton / RadioButtonInput / RadioButtonLabel 四个类全是简写原型方法(不是箭头类字段),断言写起来毫不含糊。装完直接用。

★★★★ 它的几何可以精确预言,实测逐像素命中

RadioButtonInput 的尺寸完全由几行算术决定:

var innerSize = {width: 20, height: 20, borderRadius: 10}          // 内点(选中时才渲染)
var outerSize = {width: 30, height: 30, borderRadius: 15}          // 外圈
if (this.props.buttonSize) {                                       // 传了 buttonSize
  innerSize.width = this.props.buttonSize;  innerSize.height = this.props.buttonSize;
  outerSize.width = this.props.buttonSize + 10;  outerSize.height = this.props.buttonSize + 10;
}
if (this.props.buttonOuterSize) { outerSize.width = this.props.buttonOuterSize; … }
var borderWidth = this.props.borderWidth || 3

而"选中"只是多渲染一个实心内点:

var c = <View style={[
  Style.radioNormal,
  this.props.isSelected && Style.radioActive,      // ← 只有选中时才加内点样式
  this.props.isSelected && innerSize,
  this.props.isSelected && {backgroundColor: innerColor}
]}></View>

⇒ 所以"哪一项被选中"在布局 dump 里是"结构可见"的:选中项会多一个嵌套节点。

实测(initial={1}、density 3):

节点预言实测
第 1 个外圈30vp = 90pxCustom#radioButtonInputradioButton|0 ⇒ w=90 h=90 ✓
第 2 个(initial=1)外 90px + 内 60px外 w=90 h=90 + 内 Custom#View w=60 h=60 ✓✓
第 3 个外圈90pxw=90 h=90(无内点)✓

⇒ outerSize 默认 30vp、innerSize 默认 20vp,两个都被逐像素验证 ✓✓

★★ 而且节点名自带索引(源码里 testID = 'radioButton' + '|' + i):

Custom#radioButtonInputradioButton|0     ← 第 1 项
Custom#radioButtonInputradioButton|1     ← 第 2 项
Custom#radioButtonInputradioButton|2     ← 第 3 项

⇒ 在 dump 里可以按索引精确定位每个圆点 ✓

★★★ 交互也精确验证了

点第 1 项的标签「选项甲」:

PRESS R1 value=0 index=0

⇒ 源码里是这么串起来的:

// RadioButtonLabel
onPress={() => { if (!this.props.disabled) { this.props.onPress(this.props.obj.value, this.props.index) } }}
// RadioForm._renderButton 传下去的 onPress
onPress={(value, index) => { this.props.onPress(value, index); this.setState({is_active_index: index}) }}

⇒ 回调拿到的是 obj.value 与索引(注意是 value,不是 label)—— 实测 value=0 index=0 完全吻合 ✓

clearSelection() 是 RadioForm 上的原型方法,可以用 ref 调,实测调用成功:

CLEAR r4 clearSelection() 调用成功

★★ 一处预扫抓到的"平台分支",值得留意

预扫时命中了一行 Platform.OS === 'android':

constructor(props) {
  super(props);
  this.state = { is_active_index: props.initial };
  if (Platform.OS === 'android') {
    UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true);
  }
  …
}
// 另一处:
componentDidUpdate () {
  if (this.props.animation) { LayoutAnimation.spring() }     // ← 依赖上面那个启用开关
}

⇒ 鸿蒙上 Platform.OS 是 'harmony',不是 'android' ⇒ 这一支被跳过 ⇒ LayoutAnimation 的启用开关从未被调用。
⇒ 而 Android 系平台上 LayoutAnimation 正是需要这个 opt-in 才会动 ⇒ 所以这里存在"动画不生效(选中状态瞬间切换)"的可能。

⚠️ 这一条我本轮没能实测(两帧像素差分没做成)⇒ 文章里只作为源码级疑点记录,不下结论 ✓

设备侧断言:A 20 / 20 + B 4 / 4 = 24 / 24 纯断言全通过;交互组两条为"断言先于交互执行"的顺序问题(两个行为都已用日志单独证实,见第六节)。

在这里插入图片描述


一、先说结论

项结果
上游最新版2.7.4;license: MIT;上游 star 446
库类型纯 JS 组件库(index.js 251 B 只做导出 + lib/SimpleRadioButton.js 8831 B + lib/Style.js 978 B,源码直发)
是否需要原生适配不需要(无 harmony/、无 NativeModules)
运行时依赖✅ 零
prepare / postinstall✅ 都没有
mainindex.js
需要 HAR / 权限 / ohpm❌ 都不需要
★ 是否可用✅ 可以直接用
★ 导出默认 RadioForm;具名 RadioButton / RadioButtonInput / RadioButtonLabel(4 个类)
★ 成员写法4 个类全是简写原型方法(clearSelection / shouldComponentUpdate / componentDidUpdate … 都在原型上)
设备侧断言✅ 纯断言 24 / 24(A 20 + B 4);交互证据:onPress(value,index) ✓、clearSelection() ✓
★★★★ 几何外圈 90px = 30vp、选中内点 60px = 20vp,只有被选中那一项有内点 ⇒ 逐像素命中 ✓
★ 节点命名radioButton|N ⇒ dump 里可按索引定位 ✓
★★★ 平台分支Platform.OS === 'android' ⇒ 鸿蒙跳过 LayoutAnimation 的启用(动画疑点,未实测)
★ 无害缺陷Style.js 定义 radioForm,代码里用 Style.radioFrom ⇒ undefined(数组样式里无影响)

二、判定与接入

步做法结果
①package.json 里有没有 harmony.autolinking没有
②仓库里有没有 harmony/没有
③代码里有没有 NativeModules全无
④import 图体检只有自己的两个文件 + react / react-native ⇒ 零依赖 ✓
⑤scripts 扫 prepare 家族✅ 没有
⑥★ 老 API 预扫✅ 全空(无 ViewPropTypes/createClass/contextTypes/componentWillReceiveProps)
⑦★ 平台分支预扫命中 1 处:Platform.OS === 'android'(控制 LayoutAnimation 启用)⇒ ⚠️ 需留意
⑧原生痕迹预扫✅ 空
⑨★ 成员写法预扫4 个类全是原型方法 ⇒ 断言可以照源码写 ✓
npm install "git+https://atomgit.com/oh-react-native/react-native-simple-radio-button.git#2.7.4-ohos-1.0.0" --ignore-scripts

装完即可用。


三、主要 props 与默认值(读源码整理)

RadioForm.defaultProps = {
  radio_props: [],                 // [{label, value}, …]
  initial: 0,                      // 初始选中索引(放到 state.is_active_index)
  buttonColor: '#2196f3',
  selectedButtonColor: '#2196f3',
  formHorizontal: false,           // true ⇒ 一行横向排列
  labelHorizontal: true,           // false ⇒ 标签竖排在圆点下方
  animation: true,                 // 为真时 componentDidUpdate 里调 LayoutAnimation.spring()
  labelColor: '#000',
  selectedLabelColor: '#000',
  wrapStyle: {},
  disabled: false,
};

其它可用:buttonSize(内点尺寸,外圈自动 +10)、buttonOuterSize、buttonInnerColor、buttonOuterColor、borderWidth(默认 3)、buttonStyle、buttonWrapStyle、labelStyle、labelWrapStyle、radioStyle、style、accessible、accessibilityLabel、testID、idSeparator(默认 '|')。

行为要点:

行为说明
★ 回调签名onPress(obj.value, index)(是 value,不是 label)
★ 点圆点或点标签都会回调圆点是 TouchableOpacity,标签是 TouchableWithoutFeedback
★ disabled 时圆点渲染成普通 View(无点击);标签的 onPress 也会短路
★ radio_props 为空render() 里改渲染 props.children
★ clearSelection()把 is_active_index 置 -1(清空选择),原型方法,可用 ref 调
★ initial只作为初始 state,之后是内部 state 管理

四、实测结果

4.1 断言 24 / 24(纯断言部分)

组内容结果
A 契约与结构默认导出是 class、恰好 4 个具名键、三个具名导出都是 class、RadioForm 5 个原型方法全在 prototype、RadioButton 4 个原型方法全在 prototype、clearSelection 是原型方法、defaultProps 六项取值、RadioButtonInput.defaultProps.disabled、Platform.OS、UIManager.setLayoutAnimationEnabledExperimental 的 typeof、LayoutAnimation.spring 存在、3 条源码级事实20/20
B 渲染与几何5 个实例渲染未抛错、标题安全区(vp ≥39)、两条几何预言记录4/4
C 交互与边界回调 / disabled 对照 / clearSelection / 4 条边界2/4 ⚠️ 顺序问题,见第六节

4.2 关键观测值

默认导出的 typeof = function
RadioForm 原型方法命中数 = 5 / 5     RadioButton 原型方法命中数 = 4 / 4
★★ clearSelection 是原型方法
Platform.OS = harmony
★★★ typeof UIManager.setLayoutAnimationEnabledExperimental = function(存在,但鸿蒙分支不调用它)
★ LayoutAnimation.spring 仍存在

★ R1(initial=1)的几何(density 3):
   Custom#radioButtonInputradioButton|0  w=90  h=90      ← 外圈 30vp
   Custom#radioButtonInputradioButton|1  w=90  h=90      ← 外圈 30vp
     └─ Custom#View                       w=60  h=60      ← ★★ 选中内点 20vp(唯一一个)
   Custom#radioButtonInputradioButton|2  w=90  h=90      ← 外圈 30vp(无内点)

★ 交互:PRESS R1 value=0 index=0        ← onPress(obj.value, index) ✓
★ 交互:CLEAR r4 clearSelection() 调用成功
UI 安全区:TITLE y=420px(vp ≥39 ✓)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


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

5.1 用这个库要知道的

事项说明
★ 回调是 (value, index)不是 label,也不是事件对象
★ 尺寸规则外圈默认 30vp / 内点默认 20vp;buttonSize ⇒ 内点 = 它、外圈 = 它 + 10;buttonOuterSize ⇒ 单独指定外圈
★ borderWidth 默认 3外圈边框宽度
★★ Platform.OS === 'android' 才启用 LayoutAnimation鸿蒙走不到 ⇒ 动画可能不生效(本轮未实测)
★ animation 默认 truecomponentDidUpdate 里每次更新都会调 LayoutAnimation.spring()
★ disabled 时不渲染可点元素圆点变普通 View;标签 onPress 短路
★ radio_props 为空 ⇒ 渲染 children想自己拼布局时可以用
★ clearSelection() 要拿 ref多选问卷场景常用
⚠️ 一处无害拼写错误Style.js 定义 radioForm,render() 里用 Style.radioFrom ⇒ undefined(数组样式里无影响)
⚠️ UIManager.setLayoutAnimationEnabledExperimental 用法用了 && 保护所以即使该 API 不存在也不会抛错

5.2 本次验证的边界

  1. ★★ 动画(LayoutAnimation)是否真的不生效,本轮未实测(两帧像素差分没做成);
  2. clearSelection() 之后"内点消失"未做视觉确认(R4 区在 dump 里定位失败,量到的是别的实例);
  3. disabled 的对照没有真正点到"禁用实例"(R3 在屏外)⇒ 那一条断言是空过的;
  4. buttonOuterSize 未验证(只用了默认与 buttonSize);
  5. borderWidth / buttonInnerColor / buttonOuterColor 未逐项验证;
  6. labelHorizontal=false 的竖排布局未验证;
  7. idSeparator / accessibilityLabel / testID 的拼接规则未逐项核对(只观察到节点名含 radioButton|N);
  8. children 分支只做了渲染观察(R5);
  9. RadioButton / RadioButtonInput / RadioButtonLabel 单独使用未验证(只作为 RadioForm 的内部件);
  10. 多选问卷的完整流程(连续点选 + 清空 + 再选)未跑;
  11. 只在一台模拟器上验证(Pura X View,density 3,1320×2232),没有真机;未做跨平台对照。

未改动库代码。


六、★ 两条断言失败的原因(如实记录 + 一条流程教训)

C 组两条失败:

✗ [C] ★★ 点单选项触发了 onPress(带 value 与 index)   期望 true  实际 false
✗ [C] ★ 已通过 ref 调过 clearSelection                 期望 true  实际 false

原因不是库,而是我这一侧的执行顺序:我是先点了"跑全部"(那一刻还没有任何交互),之后才去点单选项、才去调 clearSelection() ⇒ 断言读的是点击之前的计数 ✓

而这两个行为本身都已经被独立证实:

PRESS R1 value=0 index=0                 ← 点「选项甲」的回调(value 与 index 都对)
CLEAR r4 clearSelection() 调用成功        ← 用 ref 调 clearSelection

★ 教训:交互类断言必须在交互【之后】再跑一次。
更稳的做法是把"跑断言"分成两阶段:先跑静态/契约断言,做完交互后再跑交互断言;
或者让页面把断言结果实时刷新(本轮是手动触发的快照)。


七、常见问题

Q1:它在 RN 0.84 上能跑吗?

能。 纯 JS、零依赖、没有一处已移除的 API;四个类都是简写原型方法。本工程实测:打包与构建都成功、页面正常渲染、选中内点几何逐像素吻合、点击回调与 clearSelection() 都正常 ✓

Q2:怎么知道当前选中了哪一项?

两种办法:

// ① 受控:自己记住 index
<RadioForm initial={0} onPress={(value, index) => setSel(index)} … />
// ② 从布局上判断:选中项会多一个内点(默认 20vp),dump 里可数

Q3:onPress 的第一个参数是什么?

是 obj.value(radio_props 里那一项的 value),不是 label。第二个参数是索引。实测点第一项得到 value=0 index=0 ✓

Q4:想清空选择怎么办?

用 ref 调 clearSelection()(原型方法):

const ref = useRef(null);
<RadioForm ref={ref} radio_props={props} onPress={…} />
// 之后
ref.current.clearSelection();     // ⇒ is_active_index = -1

Q5:怎么改圆点大小?

<RadioForm buttonSize={24} … />        // 内点 24vp,外圈自动 34vp
<RadioForm buttonOuterSize={40} … />   // 只改外圈
<RadioForm borderWidth={2} … />        // 外圈边框(默认 3)

Q6:为什么点禁用项没反应?

因为 disabled 为真时,圆点被渲染成普通 View(不是 TouchableOpacity),标签的 onPress 里也直接短路 ⇒ 不会回调。

Q7:为什么我看不到切换动画?

源码里 LayoutAnimation 的启用开关只在 Platform.OS === 'android' 时才会调:

if (Platform.OS === 'android') {
  UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true);
}

⇒ 鸿蒙上这一支走不到。如果你的平台也需要这个 opt-in,就应当在应用侧自己启用一次,或者把 animation 关掉避免无意义调用。

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

不需要。 纯 JS、零依赖、无原生代码 —— 直接装、直接用 ✓(唯一值得留意的是上面那条 LayoutAnimation 的启用开关)


小结

react-native-simple-radio-button 是纯 JS 的单选按钮组(零依赖、源码直发)。设备侧纯断言 24 / 24 通过,几何逐像素命中。

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

  1. ★★★★ "选中状态"在布局上是【结构可见】的 —— 这让几何判据非常硬。
    源码里选中时只是多渲染一个 innerSize 的实心圆:

    this.props.isSelected && Style.radioActive,
    this.props.isSelected && innerSize,
    

    ⇒ 实测 initial={1} 时,只有第 2 项有嵌套节点,且 w=60 h=60px = 20vp;三个外圈都是 w=90 h=90px = 30vp ✓✓
    ⇒ outerSize 30 / innerSize 20 两个默认值都被逐像素验证。

  2. ★★ 节点名里带索引(radioButton|N) ⇒ 在 dump 里可以按索引精确定位每个圆点,做"A/B 两态对比"时特别方便。

  3. ★★★ 回调签名可以直接预言。 源码是 onPress(this.props.obj.value, this.props.index) ⇒ 实测 PRESS R1 value=0 index=0 ✓
    ⇒ 注意是 value 不是 label —— 这类"参数到底传什么"的细节,读源码比猜快得多。

  4. ★★ 预扫抓到的那条平台分支值得单独记。 Platform.OS === 'android' 决定 LayoutAnimation 是否启用 ⇒ 鸿蒙走不到 ⇒ 存在"动画不生效"的可能。
    ⚠️ 本轮没实测,所以只写成疑点,并给出应用侧的应对(自己启用一次,或关掉 animation)。

  5. ★ 交互类断言必须在交互之后跑。 本轮两条 C 失败全是"断言快照早于交互"造成的 —— 两个行为本身都有独立日志证据(PRESS … / CLEAR …)。
    ⇒ 流程上应把"静态断言"和"交互断言"分成两阶段,别用一次快照覆盖全部。


本篇用到的库

项内容
三方库react-native-simple-radio-button(上游 2.7.4 的鸿蒙适配版)
适配仓库https://atomgit.com/oh-react-native/react-native-simple-radio-button
适配 TAG2.7.4-ohos-1.0.0
需要 HAR / 权限 / ohpm都不需要(纯 JS、零依赖)
库类型纯 JS 组件库:单选按钮组(RadioForm + 3 个子组件)
运行时依赖✅ 零
prepare / postinstall✅ 都没有
许可证MIT
上游仓库https://github.com/moschan/react-native-simple-radio-button(★446)
宿主工程RNOH084Demo(测试页 rnAppKey = SimpleRadioButtonTestApp)
# 从适配仓库安装(零依赖)
npm install "git+https://atomgit.com/oh-react-native/react-native-simple-radio-button.git#2.7.4-ohos-1.0.0" --ignore-scripts

# 本地目录接入
npm install ../react-native-simple-radio-button --ignore-scripts
import RadioForm, {RadioButton, RadioButtonInput, RadioButtonLabel} from 'react-native-simple-radio-button';

const props = [
  {label: '选项甲', value: 0},
  {label: '选项乙', value: 1},
  {label: '选项丙', value: 2},
];

// ① 基本用法
<RadioForm
  radio_props={props}
  initial={1}                                  // 初始选中第 2 项(实测:只有它带 20vp 内点)
  buttonColor="#2196f3"
  selectedButtonColor="#D0021B"                // 选中项外圈/内点颜色
  labelColor="#333333"
  selectedLabelColor="#D0021B"
  onPress={(value, index) => console.log(value, index)}   // ★ 是 value,不是 label
/>

// ② 尺寸:buttonSize ⇒ 内点 = 它,外圈 = 它 + 10
<RadioForm radio_props={props} buttonSize={24} buttonOuterSize={40} borderWidth={2} … />

// ③ 横向一行 + 清空选择(ref 调原型方法)
const ref = useRef(null);
<RadioForm ref={ref} radio_props={props} formHorizontal onPress={…} />
// ref.current.clearSelection();     // ⇒ is_active_index = -1

// ④ 禁用(圆点渲染成普通 View,不回调)
<RadioForm radio_props={props} disabled onPress={…} />

// ⑤ radio_props 为空时会渲染 children
<RadioForm radio_props={[]}><Text>自己的布局</Text></RadioForm>

// ⚠️ 几何:外圈默认 30vp、内点默认 20vp(选中时才渲染,dump 里表现为多一个嵌套节点)
// ⚠️ 平台分支:只有 Platform.OS === 'android' 才会启用 LayoutAnimation ⇒ 鸿蒙下切换动画可能不生效
// ⚠️ 一处无害拼写错误:Style.js 定义 radioForm,render 里用 Style.radioFrom(undefined)
# 换页启动测试页 —— force-stop 不能省
hdc shell aa force-stop com.rnoh084.demo
hdc shell aa start -b com.rnoh084.demo -a EntryAbility --ps rnAppKey SimpleRadioButtonTestApp

判据的复现方式

# ① 几何:外圈 30vp=90px、选中内点 20vp=60px —— 直接数 dump 里的节点尺寸
devecocli ui layout | Select-String 'radioButtonInput'      # ⇒ w=90 h=90(每项一个)
devecocli ui layout | Select-String 'Custom#View'           # ⇒ 只有一个 w=60 h=60(选中项的内点)
# ② 选中项:节点名自带索引 radioButton|N ⇒ 可按索引定位
# ③ 交互:点标签或圆点,读回调日志
hdc shell "uitest uiInput click <x> <y>"
hdc shell "hilog -x | grep sradio-test"                     # ⇒ PRESS R1 value=0 index=0
# ④ clearSelection:拿 ref 调,读日志
#    ⇒ CLEAR r4 clearSelection() 调用成功
# ⑤ 平台分支:确认 Platform.OS 不是 'android' ⇒ LayoutAnimation 启用分支走不到

验证环境

项版本
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
宿主依赖✅ 无需补装(库零依赖)
宿主 HAP 产物entry-default-signed.hap
本次构建assembleHap 4 分 04 秒 ⇒ A 20/20 + B 4/4(打包与构建退出码都检查了)
验证规模设备侧 纯断言 24 / 24;几何 4 个节点(三个外圈 90×90px + 一个内点 60×60px,逐像素命中);交互 2 项(PRESS … value=0 index=0 / CLEAR … 调用成功);4 张截图互异

相关社区与项目地址

项目地址
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

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐