关于 react-native
面向 React Native 和 Expo 开发者的移动应用模式指南,按影响程度整理性能与架构建议,涵盖列表、动画、导航和 Expo 工作流。
主要功能
- 01按影响程度排列规则
- 02列表与滚动性能建议
- 03动画与手势模式
- 04导航和界面建议
- 05状态、数据与媒体建议
- 06Expo 工作流及测试指南
适用场景
- 01创建新的 React Native 或 Expo 应用
- 02排查列表滚动卡顿
- 03评审移动端性能问题
- 04设置 Expo 项目与测试流程
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ jezweb/ claude-skills --skill react-native安装后在 Claude Code 中输入/react-native确认已加载。
详细介绍
React Native 模式
适用于 React Native + Expo 应用的性能和架构模式。规则按影响程度排序——先修复 CRITICAL,再处理 MEDIUM。
这是一个起点。随着你构建更多移动应用,这项技能也会不断扩展。
适用场景
- 构建新的 React Native 或 Expo 应用
- 优化列表和滚动性能
- 实现动画
- 检查移动端代码中的性能问题
- 设置新的 Expo 项目
1. 列表性能(CRITICAL)
列表是 React Native 中最常见的性能问题。滚动卡顿会严重影响整个应用的体验。
| 模式 | 问题 | 修复方法 |
|---|---|---|
| 使用 ScrollView 显示数据 | <ScrollView> 会一次性渲染所有项 | 使用 <FlatList> 或 <FlashList>——采用虚拟化,只渲染可见项 |
| 缺少 keyExtractor | FlatList 未设置 keyExtractor → 导致不必要的重新渲染 | keyExtractor={(item) => item.id}——为每项提供稳定且唯一的键 |
| renderItem 过于复杂 | renderItem 中的开销较大的组件会在每次滚动时重新渲染 | 使用 React.memo 包装,或提取为独立组件 |
| renderItem 中使用内联函数 | renderItem={({ item }) => <Row onPress={() => nav(item.id)} />} | 提取处理函数:const handlePress = useCallback(...) |
| 未设置 getItemLayout | FlatList 滚动时会测量每一项(开销较大) | 为固定高度的项提供 getItemLayout:(data, index) => ({ length: 80, offset: 80 * index, index }) |
| FlashList | FlatList 表现不错,但 FlashList 更适合大型列表 | @shopify/flash-list——可直接替换,采用视图回收架构 |
| 列表中使用大图片 | 全分辨率图片在主线程解码 | 使用 expo-image,设置占位图和过渡效果,并指定尺寸 |
FlatList 检查清单
每个 FlatList 都应包含:
<FlatList
data={items}
keyExtractor={(item) => item.id}
renderItem={renderItem} // Memoised component
getItemLayout={getItemLayout} // If items are fixed height
initialNumToRender={10} // Don't render 100 items on mount
maxToRenderPerBatch={10} // Batch size for off-screen rendering
windowSize={5} // How many screens to keep in memory
removeClippedSubviews={true} // Unmount off-screen items (Android)
/>
2. 动画(HIGH)
原生动画在 UI 线程上运行。JS 动画会阻塞 JS 线程并导致卡顿。
| 模式 | 问题 | 修复方法 |
|---|---|---|
| 使用 Animated API 实现复杂动画 | Animated 在 JS 线程上运行,会阻塞交互 | 使用 react-native-reanimated——在 UI 线程上运行 |
| 布局动画 | 项目出现或消失时没有过渡效果 | LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) |
| 共享元素过渡 | 在屏幕间导航时,元素会瞬间跳转 | 使用 react-native-reanimated 的共享过渡,或 expo-router 的共享元素 |
| 手势与动画 | 拖动或滑动感觉迟缓 | 使用 react-native-gesture-handler + reanimated worklets——全部在 UI 线程上运行 |
| 测量布局 | onLayout 触发太晚,导致闪烁 | 使用带共享值的 useAnimatedStyle,实现即时响应 |
Reanimated 基础
import Animated, { useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated';
function AnimatedBox() {
const offset = useSharedValue(0);
const style = useAnimatedStyle(() => ({
transform: [{ translateX: withSpring(offset.value) }],
}));
return (
<GestureDetector gesture={panGesture}>
<Animated.View style={[styles.box, style]} />
</GestureDetector>
);
}
3. 导航(HIGH)
| 模式 | 问题 | 修复方法 |
|---|---|---|
| Expo Router | 面向 React Native 的基于文件的路由(类似 Next.js) | 使用包含 _layout.tsx 文件的 app/ 目录。新 Expo 项目优先采用此方案。 |
| 堆栈中的重型屏幕 | 每个屏幕都会在堆栈中保持挂载状态 | 对无需保持状态的屏幕使用 unmountOnBlur: true |
| 深度链接 | 应用无法响应 URL | Expo Router 会自动处理。对于裸 React Native 项目:配置 Linking API |
| 标签页徽标更新 | 聚焦标签页时,徽标计数不会更新 | 使用 useIsFocused(),或在获得焦点时重新获取数据:useFocusEffect(useCallback(...)) |
| 导航状态持久化 | 应用进入后台或被终止后会丢失位置 | 使用 AsyncStorage 配合 onStateChange + initialState |
Expo Router 结构
app/
├── _layout.tsx # Root layout (tab navigator)
├── index.tsx # Home tab
├── (tabs)/
│ ├── _layout.tsx # Tab bar config
│ ├── home.tsx
│ ├── search.tsx
│ └── profile.tsx
├── [id].tsx # Dynamic route
└── modal.tsx # Modal route
4. UI 模式(高优先级)
| 模式 | 问题 | 修复方案 |
|---|---|---|
| 安全区域 | 内容被刘海或主屏幕指示条遮挡 | 使用 react-native-safe-area-context 中的 <SafeAreaView> 或 useSafeAreaInsets() |
| 键盘避让 | 表单字段被键盘遮挡 | <KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : 'height'}> |
| 平台特定代码 | iOS 和 Android 需要不同的行为 | Platform.select({ ios: ..., android: ... }) 或 .ios.tsx / .android.tsx 文件 |
| 状态栏 | 状态栏与内容重叠或颜色不正确 | 在根布局中使用来自 expo-status-bar 的 <StatusBar style="auto" /> |
| 触控目标 | 按钮太小,难以点击 | 最小尺寸为 44x44pt。使用 hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }} |
| 触觉反馈 | 点击时没有反馈 | expo-haptics — 在重要操作时调用 Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light) |
5. 图像和媒体(中优先级)
| 模式 | 问题 | 修复方案 |
|---|---|---|
| 图像组件 | react-native 中的 <Image> 功能基础 | 使用 expo-image — 支持缓存、占位图、过渡效果和 blurhash |
| 未指定尺寸的远程图像 | 图像加载时布局会发生偏移 | 始终指定 width 和 height,或使用 aspectRatio |
| 大型图像 | Android 上发生 OOM 崩溃 | 在服务器端调整尺寸,或使用能处理内存问题的 expo-image |
| SVG | 原生不支持 SVG | 导入 SVG 时使用 react-native-svg + react-native-svg-transformer |
| 视频 | 视频播放 | 使用 expo-av 或 expo-video(较新的 API) |
6. 状态和数据(中优先级)
| 模式 | 问题 | 修复方案 |
|---|---|---|
| 使用 AsyncStorage 存储复杂数据 | 每次读取都要进行 JSON 解析和序列化 | 使用 MMKV(react-native-mmkv)——速度比 AsyncStorage 快 30 倍 |
| 全局状态 | 简单状态也需要编写大量 Redux/MobX 样板代码 | 使用 Zustand——轻量精简,与 React Native 配合出色 |
| 服务端状态 | 手动处理 fetch、加载、错误和缓存 | 使用 TanStack Query——与 Web 端相同,可在 React Native 中运行 |
| 离线优先 | 没有网络时应用无法使用 | 使用 TanStack Query persistQueryClient + MMKV;复杂离线场景则使用 WatermelonDB |
| 深层状态更新 | 更新嵌套对象时需要大量使用展开运算符 | 通过 Zustand 使用 Immer:set(produce(state => { state.user.name = 'new' })) |
7. Expo 工作流(中优先级)
| 模式 | 适用场景 | 用法 |
|---|---|---|
| 开发构建 | 需要原生模块 | npx expo run:ios 或 eas build --profile development |
| Expo Go | 快速原型开发,无需原生模块 | npx expo start — 扫描二维码 |
| EAS Build | CI/CD、应用商店构建 | eas build --platform ios --profile production |
| EAS Update | 无需应用商店审核即可快速修复 | eas update --branch production --message "Fix bug" |
| 配置插件 | 无需 eject 即可修改原生配置 | 使用 expo-build-properties 或自定义配置插件配置 app.config.ts |
| 环境变量 | 为不同构建使用不同配置 | eas.json 构建配置文件 + expo-constants |
新建项目
npx create-expo-app my-app --template tabs
cd my-app
npx expo install expo-image react-native-reanimated react-native-gesture-handler react-native-safe-area-context
8. 测试(低至中优先级)
| 工具 | 用途 | 配置 |
|---|---|---|
| Jest | 单元测试、Hook 测试 | Expo 默认包含 |
| React Native Testing Library | 组件测试 | @testing-library/react-native |
| Detox | 在真实设备或模拟器上进行 E2E 测试 | detox — Wix 的测试框架 |
| Maestro | 使用 YAML 流程进行 E2E 测试 | maestro test flow.yaml — 比 Detox 更简单 |
常见问题
| 问题 | 解决方法 |
|---|---|
| Metro 打包器缓存 | npx expo start --clear |
| Pod 安装问题(iOS) | cd ios && pod install --repo-update |
| Reanimated 无法正常工作 | 必须作为首个导入项:在根目录中使用 import 'react-native-reanimated' |
| Expo SDK 升级 | 更新 SDK 版本后运行 npx expo install --fix |
| Android 构建失败 | 检查 gradle.properties 中的内存设置:org.gradle.jvmargs=-Xmx4g |
| iOS 模拟器运行缓慢 | 使用实体设备进行性能测试——模拟器无法反映实际性能 |
评论
支持 Markdown