feat(button): Button 组件迁移至 react-native-elements - #293
Conversation
Walkthrough按钮组件从 RectButton 与手动加载态渲染切换为 TouchableNativeFeedback 和 RNEButton,统一处理 ripple、禁用状态、标题样式及加载显示,并调整内部布局对齐方式。 Changes按钮组件重构
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/button/index.tsx`:
- Around line 1-3: Update the Button import in the button component to use
Button from `@rneui/themed` instead of react-native-elements, while preserving the
existing RNEButton alias and other imports.
- Around line 19-41: Remove the outer TouchableNativeFeedback wrapper and move
the onPress and disabled behavior onto RNEButton so its internal touch handler
receives clicks. In the RNEButton configuration, only provide the Ripple
background when Platform.OS is Android, avoid calling
TouchableNativeFeedback.Ripple on iOS, and remove activeOpacity={1} so iOS
retains default press feedback.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 84447a86-de84-4cf0-806a-39ed29e0117e
📒 Files selected for processing (1)
src/components/button/index.tsx
| import { FC } from 'react'; | ||
| import { StyleSheet, TouchableNativeFeedback, View } from 'react-native'; | ||
| import { Button as RNEButton } from 'react-native-elements'; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 检查 package.json 中的 RNEUI 依赖,确认包名
cat package.json | grep -E "rneui|react-native-elements"Repository: Muxi-X/ccnubox_rn
Length of output: 213
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 仅查看该文件的相关片段
wc -l src/components/button/index.tsx
cat -n src/components/button/index.tsx | sed -n '1,120p'Repository: Muxi-X/ccnubox_rn
Length of output: 2242
🏁 Script executed:
#!/bin/bash
set -euo pipefail
wc -l src/components/button/index.tsx
cat -n src/components/button/index.tsx | sed -n '1,160p'Repository: Muxi-X/ccnubox_rn
Length of output: 2242
改为从 @rneui/themed 导入 Button
当前依赖里只有 @rneui/base / @rneui/themed,继续使用 react-native-elements 会导致模块解析失败。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/button/index.tsx` around lines 1 - 3, Update the Button import
in the button component to use Button from `@rneui/themed` instead of
react-native-elements, while preserving the existing RNEButton alias and other
imports.
| const ripplePurple = commonColors.lightPurple ?? '#B8A6F5'; | ||
|
|
||
| return ( | ||
| <RectButton | ||
| rippleColor={commonColors.lightPurple} | ||
| activeOpacity={0.6} | ||
| style={[ | ||
| styles.rect_button, | ||
| currentStyle?.button_style, | ||
| { opacity: isLoading ? 0.4 : 1 }, | ||
| style, | ||
| ]} | ||
| onPress={(...props) => { | ||
| if (!isLoading) onPress?.(...props); | ||
| }} | ||
| <TouchableNativeFeedback | ||
| background={TouchableNativeFeedback.Ripple(ripplePurple, false)} | ||
| disabled={isLoading} | ||
| onPress={() => onPress?.(true)} | ||
| > | ||
| <View accessible accessibilityRole="button" style={[styles.button]}> | ||
| {isLoading && ( | ||
| <ActivityIndicator color="#fff" style={{ marginRight: 5 }} /> // 加载指示器 | ||
| )} | ||
| <Text style={[currentStyle?.button_text_style, text_style]}> | ||
| {children} | ||
| </Text> | ||
| <View | ||
| style={[styles.rect_button, { opacity: isLoading ? 0.4 : 1 }, style]} | ||
| > | ||
| <RNEButton | ||
| activeOpacity={1} | ||
| containerStyle={[btnGlobalStyle]} | ||
| buttonStyle={styles.button} | ||
| titleStyle={[textGlobalStyle, text_style]} | ||
| loading={isLoading} | ||
| loadingProps={{ color: '#fff', style: { marginRight: 5 } }} | ||
| title={children} | ||
| accessibilityRole="button" | ||
| /> | ||
| </View> | ||
| </RectButton> | ||
| </TouchableNativeFeedback> |
There was a problem hiding this comment.
🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift
修复 iOS 平台闪退及嵌套触摸组件导致的点击失效问题
当前实现存在以下三个严重的运行时和交互风险:
- 点击失效:
RNEButton内部默认封装了触摸反馈组件(如TouchableOpacity)。在 React Native 中,最深层的触摸组件会拦截事件。由于onPress绑定在外层,内层的RNEButton会拦截点击但无响应,导致按钮彻底无法触发onPress。 - iOS 闪退:无条件调用了
TouchableNativeFeedback.Ripple,该方法在 iOS 平台上为undefined,运行到此行会导致应用直接崩溃。 - iOS 缺少交互反馈:如果强制设置
activeOpacity={1},则会导致 iOS 端在使用原生的TouchableOpacity时完全失去点击的透明度反馈。
建议移除外层的 TouchableNativeFeedback,将 onPress、disabled 等交互属性交由 RNEButton 直接接管。通过判断 Platform.OS,仅在 Android 平台启用原生的水波纹属性(将其传给 background 即可传递至底层的反馈组件),同时移除 activeOpacity={1} 以恢复 iOS 的默认按压效果。
根据提供的 relevant code snippets (src/store/visualScheme.ts),项目中包含多平台逻辑 (Platform.OS === 'ios'),因此必须妥善处理 iOS 下特有 API 的兼容性。
🐛 建议的修复方案
const ripplePurple = commonColors.lightPurple ?? '`#B8A6F5`';
+
+ // 仅在 Android 上使用 TouchableNativeFeedback.Ripple,避免 iOS 闪退
+ const background = Platform.OS === 'android'
+ ? TouchableNativeFeedback.Ripple(ripplePurple, false)
+ : undefined;
return (
- <TouchableNativeFeedback
- background={TouchableNativeFeedback.Ripple(ripplePurple, false)}
- disabled={isLoading}
- onPress={() => onPress?.(true)}
- >
- <View
- style={[styles.rect_button, { opacity: isLoading ? 0.4 : 1 }, style]}
- >
- <RNEButton
- activeOpacity={1}
- containerStyle={[btnGlobalStyle]}
- buttonStyle={styles.button}
- titleStyle={[textGlobalStyle, text_style]}
- loading={isLoading}
- loadingProps={{ color: '`#fff`', style: { marginRight: 5 } }}
- title={children}
- accessibilityRole="button"
- />
- </View>
- </TouchableNativeFeedback>
+ <View style={[styles.rect_button, { opacity: isLoading ? 0.4 : 1 }, style]}>
+ <RNEButton
+ containerStyle={[btnGlobalStyle]}
+ buttonStyle={styles.button}
+ titleStyle={[textGlobalStyle, text_style]}
+ loading={isLoading}
+ loadingProps={{ color: '`#fff`', style: { marginRight: 5 } }}
+ title={children}
+ accessibilityRole="button"
+ onPress={() => onPress?.(true)}
+ disabled={isLoading}
+ background={background}
+ />
+ </View>
);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const ripplePurple = commonColors.lightPurple ?? '#B8A6F5'; | |
| return ( | |
| <RectButton | |
| rippleColor={commonColors.lightPurple} | |
| activeOpacity={0.6} | |
| style={[ | |
| styles.rect_button, | |
| currentStyle?.button_style, | |
| { opacity: isLoading ? 0.4 : 1 }, | |
| style, | |
| ]} | |
| onPress={(...props) => { | |
| if (!isLoading) onPress?.(...props); | |
| }} | |
| <TouchableNativeFeedback | |
| background={TouchableNativeFeedback.Ripple(ripplePurple, false)} | |
| disabled={isLoading} | |
| onPress={() => onPress?.(true)} | |
| > | |
| <View accessible accessibilityRole="button" style={[styles.button]}> | |
| {isLoading && ( | |
| <ActivityIndicator color="#fff" style={{ marginRight: 5 }} /> // 加载指示器 | |
| )} | |
| <Text style={[currentStyle?.button_text_style, text_style]}> | |
| {children} | |
| </Text> | |
| <View | |
| style={[styles.rect_button, { opacity: isLoading ? 0.4 : 1 }, style]} | |
| > | |
| <RNEButton | |
| activeOpacity={1} | |
| containerStyle={[btnGlobalStyle]} | |
| buttonStyle={styles.button} | |
| titleStyle={[textGlobalStyle, text_style]} | |
| loading={isLoading} | |
| loadingProps={{ color: '#fff', style: { marginRight: 5 } }} | |
| title={children} | |
| accessibilityRole="button" | |
| /> | |
| </View> | |
| </RectButton> | |
| </TouchableNativeFeedback> | |
| const ripplePurple = commonColors.lightPurple ?? '`#B8A6F5`'; | |
| // 仅在 Android 上使用 TouchableNativeFeedback.Ripple,避免 iOS 闪退 | |
| const background = Platform.OS === 'android' | |
| ? TouchableNativeFeedback.Ripple(ripplePurple, false) | |
| : undefined; | |
| return ( | |
| <View style={[styles.rect_button, { opacity: isLoading ? 0.4 : 1 }, style]}> | |
| <RNEButton | |
| containerStyle={[btnGlobalStyle]} | |
| buttonStyle={styles.button} | |
| titleStyle={[textGlobalStyle, text_style]} | |
| loading={isLoading} | |
| loadingProps={{ color: '`#fff`', style: { marginRight: 5 } }} | |
| title={children} | |
| accessibilityRole="button" | |
| onPress={() => onPress?.(true)} | |
| disabled={isLoading} | |
| background={background} | |
| /> | |
| </View> | |
| ); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/button/index.tsx` around lines 19 - 41, Remove the outer
TouchableNativeFeedback wrapper and move the onPress and disabled behavior onto
RNEButton so its internal touch handler receives clicks. In the RNEButton
configuration, only provide the Ripple background when Platform.OS is Android,
avoid calling TouchableNativeFeedback.Ripple on iOS, and remove
activeOpacity={1} so iOS retains default press feedback.
Summary by CodeRabbit