关于 react-native-patterns
面向 React Native 开发者的组件构建技能,帮助实现易维护的移动界面。内容涵盖交互、列表、导航、无障碍功能及聊天式界面,并针对 React Native 提供实践指导。
主要功能
- 01Pressable 触控交互模式
- 02FlashList 与 ScrollView 指引
- 03底部弹层模式
- 04无障碍功能要求
- 05聊天式组件模式
- 06Expo Router 导航模式
适用场景
- 01构建交互式移动界面
- 02高效呈现动态列表
- 03添加无障碍控件与导航
- 04设计带有丰富卡片的聊天界面
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dralgorhythm/ claude-agentic-framework --skill react-native-patterns安装后在 Claude Code 中输入/react-native-patterns确认已加载。
详细介绍
React Native 模式
概述
用于构建易于维护的 React Native 组件的模式。涵盖触摸交互(Pressable)、列表渲染(FlashList)、模态框模式(底部弹出面板)、无障碍功能、导航,以及与 Web React 不同的 RN 特定要求。
有关 React Web 模式(hooks、context、组合),请参阅 react-patterns 技能。
工作流
构建交互式屏幕:
- 设置 SafeAreaView 或应用安全区域内边距
- 所有交互元素都使用 Pressable(最小尺寸为 44pt)
- 在主要操作上添加触觉反馈
- 列表使用 FlashList,固定内容使用 ScrollView
- 为详情/模态视图实现底部弹出面板
- 为所有交互元素添加无障碍标签
- 在设备上测试触摸目标和滚动性能
指南
Pressable(而非 TouchableOpacity)
Pressable 是标准的触摸组件。TouchableOpacity 已弃用。
关键原则:
- 所有交互元素的最小触摸目标尺寸为 44pt
- 使用
hitSlop将触摸区域扩展到视觉边界之外:hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }} - 通过
style函数提供反馈:style={({ pressed }) => [pressed && { opacity: 0.7 }]} - 或使用 NativeWind:
className="active:opacity-70" - 在主要操作的按下事件中添加触觉反馈:
onPress={() => { Haptics.impactAsync(); doAction(); }}
纯文本字符串规则
React Native 要求所有可见文本都必须用 <Text> 包裹:
✅ <View><Text>Hello</Text></View>
❌ <View>Hello</View> // Crashes at runtime
此规则适用于条件渲染、字符串插值和 JSX 表达式。始终使用 Text 包裹字符串。
ScrollView 模式
有两个 className 目标:
className— 外层容器(布局、背景)contentContainerClassName— 内层内容(内边距、间距、对齐)
对于内容固定、不动态变化的屏幕,使用 ScrollView。对于动态列表,使用 FlashList。
使用自定义滚动指示器时,设置 showsVerticalScrollIndicator={false} 可让视觉效果更简洁。
FlashList 模式
高性能列表渲染:
- 必须设置
estimatedItemSize— 估算项目的平均高度(单位为点) renderItem接收{ item, index }— 保持渲染函数纯净keyExtractor— 使用数据中的唯一字符串 IDcontentContainerClassName— 使用 NativeWind 设置内层内容样式ItemSeparatorComponent— 统一项目之间的间距ListEmptyComponent— 优雅地呈现空状态
将底部弹出面板用作模态框
@gorhom/bottom-sheet 可替代 Web 模态对话框:
- 定义
snapPoints数组:['25%', '50%', '90%'] - 使用
BottomSheetModal和BottomSheetModalProvider进行命令式控制 - 对可滚动的面板内容使用
BottomSheetScrollView - 使用
BottomSheetBackdrop创建按下即可关闭的遮罩层 - 显示:
bottomSheetRef.current?.present() - 关闭:
bottomSheetRef.current?.dismiss()
expo-image 模式
所有图像显示都使用它:
- 远程图像:
source={{ uri: 'https://...' }} - 本地图像:
source={require('../assets/image.png')} - Blurhash 占位符:加载状态使用
placeholder="LKO2?U%2Tw=w]~RBVZRi}" - 卡片缩略图使用
contentFit="cover",完整图像使用"contain" - 使用
transition={300}实现加载时平滑淡入
无障碍功能
交互元素必须设置以下无障碍属性:
| 属性 | 用途 | 示例 |
|---|---|---|
accessibilityRole | 语义角色 | "button"、"link"、"image" |
accessibilityLabel | 屏幕阅读器文本 | "Open settings" |
accessibilityHint | 操作说明 | "Opens the settings screen" |
accessibilityState | 动态状态 | { selected: true, disabled: false } |
accessible | 标记为可访问 | Pressable 默认为 true |
指南:
- 所有 Pressable 元素都需要
accessibilityLabel - 图像需要使用
accessibilityLabel描述其内容 - 使用
accessibilityRole="header"设置屏幕标题 - 开关/复选框:设置
accessibilityState={{ checked: isChecked }} - 禁用的元素:设置
accessibilityState={{ disabled: true }}
以聊天为核心的组件模式
演示采用以聊天为核心的界面,会话串是主要 UI。
聊天屏幕布局
分为三个区域:
- 标题栏:保持简洁——标题、返回导航和可选状态。使用顶部安全区域内边距。
- 消息流:使用 FlashList 或反转的 ScrollView 显示对话。Agent 与用户消息交替出现。富内容嵌入 Agent 消息组件中。
- 输入栏:固定在底部的文本输入框。使用
KeyboardAvoidingView(iOS)或android:windowSoftInputMode="adjustResize"。为主屏幕指示条预留底部安全区域内边距。可在文本框上方放置快捷操作标签。
消息气泡
Agent 和用户气泡使用不同的样式(对齐方式、颜色、形状):
- Agent 气泡:左对齐、中性背景,可包含子组件(卡片、摘要、操作按钮)
- 用户气泡:右对齐、强调色背景,仅含文本
- 新消息追加时,气泡使用
FadeInDown.duration(300)动画显示
内嵌富卡片
作为 Agent 消息气泡的子组件呈现的紧凑数据卡片:
- 照片缩略图(expo-image)+ 标题 + 关键指标 + 状态徽标
- 可点击——打开底部弹出层,显示完整详情
- 对于卡片组:在消息中使用水平
ScrollView,或采用垂直堆叠布局 - 交错动画:
FadeInDown.delay(index * 50).duration(300)
摘要卡片
随着对话进展而更新的累计总数/状态:
- 显示汇总状态:保留($X)、出售($Y)、捐赠($Z)、进度(N%)
- 可固定显示在输入栏上方,确保持续可见
- 更新内容时使用交叉淡入淡出效果(150ms timing)
提示卡片
包含内嵌操作按钮的 Agent 建议:
- 提示文本 + 1-3 个内联操作按钮
- 按钮使用 Pressable,触控目标为 44pt,按下时提供触觉反馈
- 点按后,提示卡片会更新以显示已确认的操作
- 例如:“要我帮你查找上门取件选项吗?” → [好的] [暂时不用]
对比视图
在消息中展示前后对比或并排内容:
- 两个带标签(之前/之后)的 expo-image 实例
- 可选的变化注释(例如,“检测到新的划痕”)
时间线视图
在 Agent 消息中内嵌呈现的按时间顺序排列的事件列表:
- 紧凑布局:每个事件包含日期 + 标题 + 图标
- 可展开:点按以显示详情(内联展开或底部弹出层)
- 事件之间使用垂直连接线
通知消息
作为新消息出现在会话串中的主动 Agent 提醒:
- 使用醒目样式(图标 + 强调色),以区别于常规 Agent 消息
- 可包含内联操作按钮
- 例如:“还有 6 周就进入风暴季。你的地下室仍显示存在潮湿风险。”
组件中的导航
使用 Expo Router 的 useRouter() 钩子:
router.push('/path')— 向前导航(添加到堆栈)router.replace('/path')— 替换当前屏幕router.back()— 返回router.canGoBack()— 检查是否可以返回- 传递参数:
router.push({ pathname: '/detail/[id]', params: { id: '123' } })
最佳实践
- 始终使用
Pressable,而不是TouchableOpacity或TouchableHighlight - 确保最小触控目标为 44pt——视觉尺寸较小时使用
hitSlop - 所有可见字符串都必须放在
<Text>中——JSX 中不能有裸字符串 - 所有图像都使用
expo-image(绝不使用 RNImage) - 为每个交互元素添加
accessibilityLabel - 对于超过 10 项的列表,使用带有
estimatedItemSize的FlashList - 使用
contentContainerClassName设置 ScrollView/FlashList 内部样式 - 少量添加触觉反馈——仅用于主要操作
- 在真实设备上测试触控目标(模拟器触控不够精确)
反模式
- 使用
TouchableOpacity(已弃用 — 请使用 Pressable) - 触控目标小于 44pt,且未使用
hitSlop补偿 - 裸字符串未用
<Text>包裹(会导致运行时崩溃) - 使用 RN
Image而非expo-image(没有缓存,也不支持 blurhash) - 忘记为 FlashList 设置
estimatedItemSize - 对大型数据集使用
FlatList(FlashList 更快) - 交互元素缺少
accessibilityLabel - 使用
Alert.alert()显示复杂选项(请改用底部弹出面板) - 在 ScrollView 的
className中设置内边距(请使用contentContainerClassName) - 在 FlashList 的
renderItem中内联匿名函数(会导致重复渲染 — 请提取为组件)