React Native 跨平台开发
React Native 基础
RN 架构演进
React Native 的架构经历了从传统架构到新架构(New Architecture)的重大演进。
传统架构
传统架构基于 Bridge(桥接)机制,JavaScript 线程与原生线程通过 Bridge 进行异步序列化通信:
JS 线程 <--> Bridge (序列化 JSON) <--> 原生线程核心组件:
- JavaScriptCore(JSC):iOS 上默认使用 JavaScriptCore 引擎执行 JS 代码,Android 也内置了 JavaScriptCore。JSC 是 WebKit 项目的一部分,提供 JavaScript 执行环境。
- Hermes 引擎:Facebook 为 React Native 专门打造的 JS 引擎,专为移动端优化。特性包括:预编译字节码(预编译为字节码,减少 JS 解析时间)、更小的二进制体积、更低的内存占用、更快的启动时间。启用方式:
typescript
// metro.config.js
module.exports = {
resolver: {/* ... */},
transformer: {/* ... */},
};
// android/app/build.gradle
project.ext.react = [
enableHermes: true, // 打开 Hermes
]
// iOS Podfile
:hermes_enabled => true- Bridge(桥接):传统的通信机制。JS 端将调用序列化为 JSON 消息,通过 Bridge 发送到原生端,原生端反序列化后执行操作,再将结果序列化返回。缺点是所有通信都是异步的,且序列化/反序列化有性能开销。
- Fabric Renderer:新架构中的 UI 渲染系统。相比传统的 Shadow Tree 渲染方式,Fabric 让 JS 和原生共享布局信息,支持同步渲染,减少 UI 更新延迟。
- TurboModules:新架构中的原生模块系统。相比传统的 Native Modules,TurboModules 支持懒加载(按需加载原生模块),并使用 JSI 实现 JS 与原生之间的直接调用,无需 Bridge 序列化。
JSI(JavaScript Interface)
JSI 是新架构的核心,它允许 JS 直接持有原生对象的引用并同步调用其方法,无需通过 Bridge 异步序列化:
JS 线程 <--> JSI(直接内存引用) <--> C++ 宿主对象 <--> 原生模块关键优势:
- 同步调用,无需序列化
- 更低的开销
- 支持跨线程共享对象
Bridge 与 JSI 对比
| 特性 | Bridge | JSI |
|---|---|---|
| 通信方式 | 异步 JSON 序列化 | 同步直接调用 |
| 性能 | 高延迟,大数据量场景瓶颈明显 | 低延迟,接近原生调用 |
| 内存 | 每个消息都需要拷贝 | 共享内存引用 |
| 类型安全 | 无 | 可通过 C++ 中间层保证 |
| 兼容性 | 稳定成熟 | 需要适配新架构 |
Bridgeless 新架构
Bridgeless 模式是 React Native 新架构的最终目标,完全移除 Bridge,所有通信通过 JSI 进行:
- Fabric Renderer:使用 JSI 直接与 C++ 的 UIManager 通信,支持同步布局计算。
- TurboModules:原生模块通过 JSI 直接暴露给 JS,按需加载,减少启动时间。
- CodeGen:通过 CodeGen 自动生成原生和 JS 之间的类型安全接口代码。
New Architecture 启用
typescript
// 在 react-native.config.js 中启用新架构
module.exports = {
project: {
ios: {},
android: {},
},
// 启用新架构
newArchEnabled: true,
};typescript
// android/gradle.properties
newArchEnabled=true
// iOS Podfile
ENV['RCT_NEW_ARCH_ENABLED'] = '1'项目结构
一个标准的 React Native 项目结构如下:
MyReactNativeApp/
├── package.json # 项目依赖配置
├── index.js # 应用入口文件
├── App.tsx # 根组件
├── metro.config.js # Metro 打包器配置
├── babel.config.js # Babel 转译配置
├── tsconfig.json # TypeScript 配置
├── .eslintrc.js # ESLint 配置
├── .prettierrc.js # Prettier 配置
├── android/ # Android 原生工程目录
│ ├── app/
│ │ ├── src/main/java/ # Java/Kotlin 源码
│ │ │ └── com/myapp/
│ │ │ └── MainApplication.kt
│ │ │ └── MainActivity.kt
│ │ └── build.gradle
│ ├── build.gradle
│ ├── settings.gradle
│ └── gradle.properties
├── ios/ # iOS 原生工程目录
│ ├── MyReactNativeApp/
│ │ ├── AppDelegate.mm # iOS 应用代理
│ │ ├── Info.plist
│ │ └── LaunchScreen.storyboard
│ ├── Podfile # CocoaPods 依赖
│ └── MyReactNativeApp.xcodeproj
└── __tests__/ # 测试目录
├── App.test.tsx
└── components/关键文件说明:
- package.json:声明项目名称、版本、依赖包和脚本命令。
- index.js:应用入口,调用
AppRegistry.registerComponent注册根组件。 - App.tsx:根组件,通常包含导航容器和全局 Provider。
- metro.config.js:Metro 打包器配置,可配置转换器、解析器、缓存等。
- babel.config.js:Babel 转译配置,通常包含
@react-native/babel-preset和 Reanimated 插件。
typescript
// package.json
{
"name": "MyReactNativeApp",
"version": "1.0.0",
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"start": "react-native start",
"test": "jest",
"lint": "eslint ."
},
"dependencies": {
"react": "18.2.0",
"react-native": "0.76.0",
"@react-navigation/native": "^6.1.0",
"@react-navigation/native-stack": "^6.9.0",
"react-native-screens": "^3.29.0",
"react-native-safe-area-context": "^4.8.0"
},
"devDependencies": {
"@types/react": "^18.2.0",
"typescript": "^5.0.0",
"jest": "^29.0.0",
"@react-native/babel-preset": "^0.76.0",
"@react-native/metro-config": "^0.76.0"
}
}typescript
// index.js
import { AppRegistry } from 'react-native';
import App from './App';
import { name as appName } from './app.json';
AppRegistry.registerComponent(appName, () => App);typescript
// metro.config.js
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
const config = {};
module.exports = mergeConfig(getDefaultConfig(__dirname), config);typescript
// babel.config.js
module.exports = {
presets: ['module:@react-native/babel-preset'],
plugins: ['react-native-reanimated/plugin'],
};JSX 与核心组件
React Native 使用 JSX 语法描述 UI,与 React Web 类似,但使用原生组件替代 HTML 标签。
基础组件
typescript
import React, { useState } from 'react';
import {
View,
Text,
Image,
ScrollView,
FlatList,
SectionList,
TextInput,
TouchableOpacity,
TouchableHighlight,
Pressable,
Modal,
ActivityIndicator,
SafeAreaView,
StatusBar,
StyleSheet,
} from 'react-native';
const MyComponent: React.FC = () => {
const [text, setText] = useState('');
const flatListData = [
{ id: '1', title: 'Item 1' },
{ id: '2', title: 'Item 2' },
];
const sectionData = [
{
title: 'Section A',
data: ['Item A1', 'Item A2'],
},
];
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" backgroundColor="#fff" />
{/* View:基础的布局容器,类似 HTML 的 div */}
<View style={styles.row}>
{/* Text:文本显示组件 */}
<Text style={styles.title}>Hello React Native</Text>
{/* Image:图片显示 */}
<Image
source={{ uri: 'https://example.com/image.png' }}
style={styles.image}
resizeMode="cover"
/>
</View>
{/* TextInput:文本输入框 */}
<TextInput
style={styles.input}
value={text}
onChangeText={setText}
placeholder="请输入内容"
secureTextEntry={false}
autoCapitalize="none"
/>
{/* ScrollView:可滚动容器 */}
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.scrollContent}
>
<View style={styles.card} />
<View style={styles.card} />
</ScrollView>
{/* FlatList:高性能长列表 */}
<FlatList
data={flatListData}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <Text>{item.title}</Text>}
refreshControl={<RefreshControl refreshing={false} />}
onEndReached={() => console.log('load more')}
onEndReachedThreshold={0.5}
/>
{/* SectionList:分组列表 */}
<SectionList
sections={sectionData}
keyExtractor={(item, index) => item + index}
renderItem={({ item }) => <Text>{item}</Text>}
renderSectionHeader={({ section }) => (
<Text style={styles.sectionHeader}>{section.title}</Text>
)}
/>
{/* TouchableOpacity:带透明度反馈的触摸组件 */}
<TouchableOpacity
style={styles.button}
onPress={() => console.log('pressed')}
activeOpacity={0.7}
disabled={false}
>
<Text style={styles.buttonText}>TouchableOpacity</Text>
</TouchableOpacity>
{/* TouchableHighlight:带高亮背景的触摸组件 */}
<TouchableHighlight
style={styles.button}
onPress={() => console.log('highlight pressed')}
underlayColor="#ddd"
activeOpacity={0.8}
>
<Text style={styles.buttonText}>TouchableHighlight</Text>
</TouchableHighlight>
{/* Pressable:更灵活的按压交互组件 */}
<Pressable
style={({ pressed }) => [
styles.button,
{ opacity: pressed ? 0.7 : 1 },
]}
onPress={() => console.log('pressable pressed')}
onLongPress={() => console.log('long press')}
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
>
<Text style={styles.buttonText}>Pressable</Text>
</Pressable>
{/* Modal:模态框 */}
<Modal
visible={false}
animationType="slide"
transparent={true}
onRequestClose={() => {}}
>
<View style={styles.modalContent}>
<Text>Modal Content</Text>
</View>
</Modal>
{/* ActivityIndicator:加载指示器 */}
<ActivityIndicator size="large" color="#0000ff" animating={true} />
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: { flex: 1, padding: 16 },
row: { flexDirection: 'row', alignItems: 'center' },
title: { fontSize: 18, fontWeight: 'bold', color: '#333' },
image: { width: 100, height: 100, borderRadius: 8 },
input: {
height: 44,
borderWidth: 1,
borderColor: '#ccc',
borderRadius: 8,
paddingHorizontal: 12,
},
scrollContent: { paddingHorizontal: 8 },
card: { width: 120, height: 80, backgroundColor: '#eee', marginHorizontal: 4, borderRadius: 8 },
sectionHeader: { fontSize: 16, fontWeight: '600', backgroundColor: '#f5f5f5', padding: 8 },
button: {
backgroundColor: '#007AFF',
padding: 12,
borderRadius: 8,
alignItems: 'center',
marginVertical: 4,
},
buttonText: { color: '#fff', fontSize: 16 },
modalContent: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: 'rgba(0,0,0,0.5)' },
});
export default MyComponent;Props 与 State 区别
| 特性 | Props | State |
|---|---|---|
| 定义 | 外部传入的参数,由父组件传递 | 组件内部管理的数据 |
| 可变性 | 不可变(只读),组件内不能修改 | 可变,通过 setState 或 useState 更新 |
| 触发渲染 | 父组件重新渲染传递新 props 时 | 调用 setState 或 dispatch 时 |
| 所有权 | 属于父组件 | 属于组件自身 |
| 默认值 | 通过 defaultProps 设置 | 通过 useState(initialValue) 初始化 |
| 用途 | 配置组件行为、传递数据 | 管理组件内部交互状态 |
typescript
// Props 示例 - 父组件传递数据
interface UserCardProps {
name: string;
age: number;
isActive: boolean;
onPress: (id: string) => void;
}
const UserCard: React.FC<UserCardProps> = ({ name, age, isActive, onPress }) => {
return (
<Pressable onPress={() => onPress('123')}>
<Text>{name} - {age}</Text>
<Text>{isActive ? '在线' : '离线'}</Text>
</Pressable>
);
};
// State 示例 - 组件内部管理状态
const Counter: React.FC = () => {
const [count, setCount] = useState(0);
return (
<View>
<Text>计数: {count}</Text>
<TouchableOpacity onPress={() => setCount((prev) => prev + 1)}>
<Text>增加</Text>
</TouchableOpacity>
</View>
);
};样式与布局
Flexbox 布局
React Native 使用 Flexbox 布局模型,与 CSS Flexbox 基本一致,但默认值为 flexDirection: 'column'(Web 默认为 row)。
typescript
import { View, StyleSheet } from 'react-native';
const FlexboxDemo: React.FC = () => {
return (
<View style={styles.container}>
{/* flexDirection: 主轴方向(row | column | row-reverse | column-reverse) */}
<View style={styles.row}>
<View style={styles.box} />
<View style={styles.box} />
</View>
{/* justifyContent: 主轴对齐方式 */}
<View style={styles.justifyCenter}>
<View style={styles.boxSm} />
<View style={styles.boxSm} />
</View>
{/* alignItems: 交叉轴对齐方式 */}
<View style={styles.alignCenter}>
<View style={styles.boxSm} />
<View style={styles.boxSm} />
</View>
{/* flexWrap: 换行 */}
<View style={styles.wrapContainer}>
{Array.from({ length: 6 }).map((_, i) => (
<View key={i} style={styles.wrapBox} />
))}
</View>
{/* alignSelf: 单个元素覆盖 alignItems */}
<View style={styles.row}>
<View style={styles.boxSm} />
<View style={[styles.boxSm, { alignSelf: 'flex-end' }]} />
</View>
</View>
);
};
const styles = StyleSheet.create({
container: { flex: 1, padding: 16 },
row: { flexDirection: 'row' },
box: { width: 50, height: 50, backgroundColor: '#007AFF', margin: 4 },
boxSm: { width: 40, height: 40, backgroundColor: '#34C759', margin: 4 },
justifyCenter: { flexDirection: 'row', justifyContent: 'center' },
alignCenter: { flexDirection: 'row', alignItems: 'center', height: 80 },
wrapContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
},
wrapBox: {
width: 60,
height: 60,
backgroundColor: '#FF9500',
margin: 4,
},
});flexGrow / flexShrink / flexBasis:
typescript
const FlexGrowDemo: React.FC = () => (
<View style={{ flexDirection: 'row' }}>
{/* flexGrow: 分配剩余空间的比例 */}
<View style={{ flex: 1, height: 50, backgroundColor: 'red' }} />
<View style={{ flex: 2, height: 50, backgroundColor: 'blue' }} />
{/* flexShrink: 空间不足时收缩的比例 */}
<View style={{ flexShrink: 0, width: 200, height: 50, backgroundColor: 'green' }} />
</View>
);position 定位:
typescript
const PositionDemo: React.FC = () => (
<View style={{ width: 200, height: 200, backgroundColor: '#eee' }}>
{/* relative: 相对定位,占据文档流位置 */}
<View
style={{
position: 'relative',
top: 10,
left: 10,
width: 50,
height: 50,
backgroundColor: 'red',
}}
/>
{/* absolute: 绝对定位,相对于最近的非 static 父元素 */}
<View
style={{
position: 'absolute',
top: 0,
right: 0,
width: 40,
height: 40,
backgroundColor: 'blue',
}}
/>
{/* zIndex: 层叠顺序 */}
<View
style={{
position: 'absolute',
top: 20,
left: 20,
width: 50,
height: 50,
backgroundColor: 'green',
zIndex: 10,
}}
/>
</View>
);StyleSheet.create
StyleSheet.create 是 React Native 推荐的样式定义方式,提供性能优化和类型提示。
typescript
import { StyleSheet, Platform, View, Text } from 'react-native';
// StyleSheet.create 创建样式对象
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
// 平台区分
...Platform.select({
ios: { shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1 },
android: { elevation: 4 },
}),
},
title: {
fontSize: 18,
fontWeight: '600',
color: '#333',
},
});
// 样式复用 - 合并多个样式
const combinedStyles = StyleSheet.create({
base: {
fontSize: 14,
color: '#333',
padding: 8,
},
primary: {
color: '#007AFF',
fontWeight: 'bold',
},
danger: {
color: '#FF3B30',
},
});
const StyledComponent: React.FC<{ type?: 'primary' | 'danger' }> = ({ type }) => {
// 条件样式
const textStyle = [
combinedStyles.base,
type === 'primary' && combinedStyles.primary,
type === 'danger' && combinedStyles.danger,
];
return <Text style={textStyle}>条件样式示例</Text>;
};StyleSheet.absoluteFill:
typescript
// 等效于 position: absolute, top: 0, right: 0, bottom: 0, left: 0
const overlay = StyleSheet.create({
overlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
},
});Platform 模块:
typescript
import { Platform, PlatformColor } from 'react-native';
// Platform.OS: 返回 'ios' | 'android' | 'windows' | 'macos' | 'web'
const isIOS = Platform.OS === 'ios';
const isAndroid = Platform.OS === 'android';
// Platform.select: 根据平台选择值
const styles = StyleSheet.create({
container: {
paddingTop: Platform.select({
ios: 44,
android: 24,
default: 16,
}),
},
});
// Platform.Version: 平台版本号
const isIOS15Plus = Platform.OS === 'ios' && parseInt(Platform.Version as string, 10) >= 15;
const isAndroid12Plus = Platform.OS === 'android' && Platform.Version >= 31;
// PlatformColor: 使用系统原生颜色(iOS 13+)
const nativeColor = PlatformColor('systemBlue');响应式设计
typescript
import {
Dimensions,
useWindowDimensions,
ScaledSize,
PixelRatio,
StatusBar,
StyleSheet,
View,
Text,
} from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
// Dimensions API - 获取屏幕尺寸
const screenWidth = Dimensions.get('window').width;
const screenHeight = Dimensions.get('window').height;
const screenScale = Dimensions.get('window').scale;
// 监听尺寸变化(横竖屏切换)
Dimensions.addEventListener('change', ({ window }: { window: ScaledSize }) => {
console.log('屏幕尺寸变化:', window.width, window.height);
});
const ResponsiveComponent: React.FC = () => {
// useWindowDimensions: 响应式获取窗口尺寸,随屏幕变化自动更新
const { width, height, scale, fontScale } = useWindowDimensions();
// useSafeAreaInsets: 获取安全区域 insets
const insets = useSafeAreaInsets();
const isLandscape = width > height;
// 基于屏幕宽度的响应式尺寸
const cardWidth = (width - 48) / 2; // 两列布局
const fontSize = PixelRatio.roundToNearestPixel(16);
// 字体缩放适配
const scaledFontSize = fontSize / fontScale;
return (
<View
style={[
styles.container,
{
// 横竖屏适配
flexDirection: isLandscape ? 'row' : 'column',
paddingTop: insets.top,
paddingBottom: insets.bottom,
paddingLeft: insets.left,
paddingRight: insets.right,
},
]}
>
{/* 状态栏适配 */}
<StatusBar
barStyle="dark-content"
backgroundColor="transparent"
translucent={false}
/>
<Text>屏幕宽度: {width}</Text>
<Text>屏幕高度: {height}</Text>
<Text>像素比: {PixelRatio.get()}</Text>
<Text>字体缩放: {PixelRatio.getFontScale()}</Text>
<Text>是否横屏: {isLandscape ? '是' : '否'}</Text>
</View>
);
};常用响应式工具函数
typescript
// utils/responsive.ts
import { Dimensions, PixelRatio, Platform, StatusBar } from 'react-native';
const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window');
// 基于设计稿宽度(375)的缩放
const scaleWidth = SCREEN_WIDTH / 375;
export const responsiveSize = (size: number): number => {
return PixelRatio.roundToNearestPixel(size * scaleWidth);
};
// 状态栏高度
export const STATUSBAR_HEIGHT = Platform.select({
ios: 44,
android: StatusBar.currentHeight ?? 24,
default: 24,
});
// 底部安全区域(iPhone X 及以上)
export const BOTTOM_SAFE_AREA = Platform.OS === 'ios' && SCREEN_HEIGHT >= 812 ? 34 : 0;导航与路由
React Navigation
React Navigation 是 React Native 官方推荐的导航解决方案。
安装与基础配置
bash
npm install @react-navigation/native @react-navigation/native-stack @react-navigation/bottom-tabs @react-navigation/drawer
npm install react-native-screens react-native-safe-area-context react-native-gesture-handler react-native-reanimatedtypescript
// App.tsx - 导航容器配置
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { createDrawerNavigator } from '@react-navigation/drawer';
// 定义导航参数类型
type RootStackParamList = {
Home: undefined;
Profile: { userId: string };
Settings: undefined;
};
type TabParamList = {
HomeTab: undefined;
ProfileTab: undefined;
};
// Stack Navigator(原生栈导航)
const Stack = createNativeStackNavigator<RootStackParamList>();
// Tab Navigator(底部标签导航)
const Tab = createBottomTabNavigator<TabParamList>();
// Drawer Navigator(抽屉导航)
const Drawer = createDrawerNavigator();
// NativeStackNavigator vs StackNavigator 区别
// - NativeStackNavigator: 使用系统原生导航(iOS UINavigationController / Android Fragment),性能更好,动画更流畅
// - StackNavigator (JS): 使用 JavaScript 实现的导航,可定制性更强,支持自定义动画
// - 推荐使用 NativeStackNavigator 以获得原生性能
const App: React.FC = () => {
return (
<NavigationContainer>
<Stack.Navigator
initialRouteName="Home"
screenOptions={{
headerStyle: { backgroundColor: '#007AFF' },
headerTintColor: '#fff',
headerTitleStyle: { fontWeight: 'bold' },
// 自定义动画
animation: 'slide_from_right',
}}
>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{ title: '首页' }}
/>
<Stack.Screen
name="Profile"
component={ProfileScreen}
options={({ route }) => ({
title: `用户 ${route.params.userId}`,
})}
/>
</Stack.Navigator>
</NavigationContainer>
);
};导航参数传递与类型安全
typescript
// 类型定义
type RootStackParamList = {
Home: undefined;
ProductDetail: { productId: string; title: string };
Cart: { items?: CartItem[] };
};
const Stack = createNativeStackNavigator<RootStackParamList>();
// 发送方 - 传递参数
const HomeScreen: React.FC<NativeStackScreenProps<RootStackParamList, 'Home'>> = ({
navigation,
}) => {
const goToDetail = () => {
navigation.navigate('ProductDetail', {
productId: '123',
title: '商品标题',
});
};
// 传递回调参数(通过 params)
const goToCart = () => {
navigation.navigate('Cart', {
items: [{ id: '1', name: 'Item', quantity: 1 }],
});
};
return (
<View>
<TouchableOpacity onPress={goToDetail}>
<Text>查看商品详情</Text>
</TouchableOpacity>
</View>
);
};
// 接收方 - 获取参数
const ProductDetailScreen: React.FC<
NativeStackScreenProps<RootStackParamList, 'ProductDetail'>
> = ({ route, navigation }) => {
const { productId, title } = route.params;
// 更新参数
React.useEffect(() => {
navigation.setParams({ title: `已更新: ${title}` });
}, []);
return (
<View>
<Text>{title}</Text>
<Text>商品 ID: {productId}</Text>
</View>
);
};深层链接(Deep Linking)
typescript
// 深层链接配置
const linking = {
prefixes: ['myapp://', 'https://myapp.com'],
config: {
screens: {
Home: 'home',
Profile: 'user/:userId',
ProductDetail: {
path: 'product/:productId',
parse: {
productId: (id: string) => id,
},
},
},
},
};
// 使用
<NavigationContainer linking={linking}>
{/* ... */}
</NavigationContainer>;
// 触发的 URL 示例
// myapp://user/123
// https://myapp.com/product/abcHeader 定制
typescript
<Stack.Screen
name="CustomHeader"
component={CustomHeaderScreen}
options={({ navigation, route }) => ({
headerTitle: '自定义标题',
headerStyle: { backgroundColor: '#f4511e' },
headerTintColor: '#fff',
headerTitleStyle: { fontWeight: 'bold', fontSize: 18 },
// 自定义右侧按钮
headerRight: () => (
<TouchableOpacity onPress={() => alert('设置')} style={{ marginRight: 16 }}>
<Text style={{ color: '#fff' }}>设置</Text>
</TouchableOpacity>
),
// 隐藏返回按钮
headerBackVisible: false,
// 自定义返回按钮
headerBackTitle: '返回',
// 大标题模式(iOS 11+)
headerLargeTitle: true,
})}
/>标签栏定制
typescript
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Text } from 'react-native';
const Tab = createBottomTabNavigator();
const TabNavigator: React.FC = () => {
return (
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
// 自定义图标(可使用 react-native-vector-icons 等库)
const iconMap: Record<string, string> = {
Home: 'H',
Profile: 'P',
};
return (
<Text style={{ color, fontSize: size }}>
{iconMap[route.name] || '?'}
</Text>
);
},
tabBarActiveTintColor: '#007AFF',
tabBarInactiveTintColor: 'gray',
tabBarStyle: {
backgroundColor: '#fff',
borderTopWidth: 1,
borderTopColor: '#e0e0e0',
paddingBottom: 4,
height: 56,
},
tabBarLabelStyle: {
fontSize: 12,
fontWeight: '500',
},
// 隐藏标签栏
tabBarBadge: undefined,
})}
>
<Tab.Screen
name="HomeTab"
component={HomeScreen}
options={{
tabBarLabel: '首页',
tabBarBadge: 3, // 徽标数
}}
/>
<Tab.Screen
name="ProfileTab"
component={ProfileScreen}
options={{
tabBarLabel: '我的',
}}
/>
</Tab.Navigator>
);
};嵌套导航
typescript
// 嵌套导航结构:Tab > Stack
const HomeStack = createNativeStackNavigator();
const TabNav = createBottomTabNavigator();
const HomeStackScreen: React.FC = () => (
<HomeStack.Navigator>
<HomeStack.Screen name="Home" component={HomeScreen} />
<HomeStack.Screen name="ProductDetail" component={ProductDetailScreen} />
</HomeStack.Navigator>
);
const MainTabScreen: React.FC = () => (
<TabNav.Navigator>
<TabNav.Screen name="Home" component={HomeStackScreen} options={{ headerShown: false }} />
<TabNav.Screen name="Profile" component={ProfileScreen} />
</TabNav.Navigator>
);
// 从嵌套屏幕中导航到其他 Tab 的屏幕
const handleNavigate = () => {
// navigation.navigate('Profile', { screen: 'EditProfile' });
navigation.navigate('Profile' as never);
};导航守卫与认证流
typescript
// AuthContext.tsx - 认证上下文
import React, { createContext, useContext, useState, useEffect } from 'react';
import { authService } from '../services/auth';
interface AuthContextType {
isAuthenticated: boolean;
isLoading: boolean;
user: User | null;
signIn: (credentials: Credentials) => Promise<void>;
signOut: () => Promise<void>;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export const useAuth = (): AuthContextType => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [user, setUser] = useState<User | null>(null);
// 认证状态监听
useEffect(() => {
const checkAuth = async () => {
try {
const currentUser = await authService.getCurrentUser();
setIsAuthenticated(!!currentUser);
setUser(currentUser);
} catch {
setIsAuthenticated(false);
setUser(null);
} finally {
setIsLoading(false);
}
};
checkAuth();
}, []);
const signIn = async (credentials: Credentials) => {
const user = await authService.login(credentials);
setUser(user);
setIsAuthenticated(true);
};
const signOut = async () => {
await authService.logout();
setUser(null);
setIsAuthenticated(false);
};
return (
<AuthContext.Provider value={{ isAuthenticated, isLoading, user, signIn, signOut }}>
{children}
</AuthContext.Provider>
);
};typescript
// RootNavigator.tsx - 条件导航(认证流控制)
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { ActivityIndicator, View } from 'react-native';
import { useAuth } from './AuthContext';
import { LoginScreen } from '../screens/LoginScreen';
import { MainTabScreen } from './MainTabNavigator';
const AuthStack = createNativeStackNavigator();
const RootStack = createNativeStackNavigator();
const AuthNavigator: React.FC = () => (
<AuthStack.Navigator screenOptions={{ headerShown: false }}>
<AuthStack.Screen name="Login" component={LoginScreen} />
<AuthStack.Screen name="Register" component={RegisterScreen} />
<AuthStack.Screen name="ForgotPassword" component={ForgotPasswordScreen} />
</AuthStack.Navigator>
);
export const RootNavigator: React.FC = () => {
const { isAuthenticated, isLoading } = useAuth();
// 加载中显示启动屏
if (isLoading) {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<ActivityIndicator size="large" />
</View>
);
}
return (
<RootStack.Navigator screenOptions={{ headerShown: false }}>
{isAuthenticated ? (
// 已登录:主应用
<RootStack.Screen name="Main" component={MainTabScreen} />
) : (
// 未登录:认证流程
<RootStack.Screen name="Auth" component={AuthNavigator} />
)}
</RootStack.Navigator>
);
};
// 路由守卫示例 - 需要认证才能访问的页面
const ProtectedScreen: React.FC = () => {
const { isAuthenticated } = useAuth();
if (!isAuthenticated) {
return <Text>请先登录</Text>;
}
return <Text>受保护的内容</Text>;
};网络与存储
网络请求
Fetch API
typescript
// 基础 GET 请求
const fetchUsers = async (): Promise<User[]> => {
const response = await fetch('https://api.example.com/users', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer token',
},
});
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return response.json();
};
// POST 请求
const createUser = async (data: CreateUserInput): Promise<User> => {
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
return response.json();
};Axios
bash
npm install axiostypescript
import axios, { AxiosInstance, AxiosError, AxiosRequestConfig } from 'axios';
// 创建实例
const apiClient: AxiosInstance = axios.create({
baseURL: 'https://api.example.com',
timeout: 15000,
headers: {
'Content-Type': 'application/json',
},
});
// 请求拦截器 - 添加 token
apiClient.interceptors.request.use(
(config) => {
const token = getStoredToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// 响应拦截器 - 统一错误处理
apiClient.interceptors.response.use(
(response) => response,
(error: AxiosError) => {
if (error.response?.status === 401) {
// Token 过期,重定向到登录
handleTokenExpired();
}
return Promise.reject(error);
}
);
// API 服务封装
export const userApi = {
getUsers: (params?: PaginationParams) =>
apiClient.get<User[]>('/users', { params }),
getUserById: (id: string) =>
apiClient.get<User>(`/users/${id}`),
createUser: (data: CreateUserInput) =>
apiClient.post<User>('/users', data),
updateUser: (id: string, data: Partial<User>) =>
apiClient.put<User>(`/users/${id}`, data),
deleteUser: (id: string) =>
apiClient.delete(`/users/${id}`),
};React Query / TanStack Query
bash
npm install @tanstack/react-querytypescript
import { QueryClient, QueryClientProvider, useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 数据过期时间 5 分钟
gcTime: 30 * 60 * 1000, // 缓存保留时间 30 分钟(beta 中替代 cacheTime)
retry: 3, // 错误重试次数
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 10000), // 指数退避
refetchOnWindowFocus: false, // 移动端无窗口焦点概念
},
},
});
// 在根组件包裹
const App: React.FC = () => (
<QueryClientProvider client={queryClient}>
<NavigationContainer>{/* ... */}</NavigationContainer>
</QueryClientProvider>
);
// 数据获取 Hook
const useUsers = (page: number) => {
return useQuery({
queryKey: ['users', page],
queryFn: async () => {
const response = await apiClient.get<PaginatedResult<User>>('/users', {
params: { page, pageSize: 20 },
});
return response.data;
},
// 预取下一页数据
placeholderData: keepPreviousData,
});
};
// 数据变更 Mutation
const useCreateUser = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: CreateUserInput) => userApi.createUser(data),
// 乐观更新
onMutate: async (newUser) => {
await queryClient.cancelQueries({ queryKey: ['users'] });
const previousUsers = queryClient.getQueryData<User[]>(['users']);
queryClient.setQueryData<User[]>(['users'], (old) => [
...(old || []),
{ ...newUser, id: 'temp-id' },
]);
return { previousUsers };
},
// 乐观更新失败回滚
onError: (err, newUser, context) => {
if (context?.previousUsers) {
queryClient.setQueryData(['users'], context.previousUsers);
}
},
// 成功后刷新
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
};
// 分页示例
const UsersList: React.FC = () => {
const [page, setPage] = useState(1);
const { data, isLoading, isFetching, error } = useUsers(page);
// 预取下一页
const queryClient = useQueryClient();
useEffect(() => {
if (data?.hasMore) {
queryClient.prefetchQuery({
queryKey: ['users', page + 1],
queryFn: async () => {
const res = await apiClient.get('/users', { params: { page: page + 1, pageSize: 20 } });
return res.data;
},
});
}
}, [data, page, queryClient]);
if (isLoading) return <ActivityIndicator />;
if (error) return <Text>加载失败</Text>;
return (
<FlatList
data={data?.items}
renderItem={({ item }) => <Text>{item.name}</Text>}
onEndReached={() => data?.hasMore && setPage(page + 1)}
/>
);
};数据持久化
AsyncStorage vs MMKV
| 特性 | AsyncStorage | MMKV |
|---|---|---|
| 底层实现 | React Native 内置(基于原生存储) | 腾讯开源的 mmap 内存映射存储 |
| 读写性能 | 较慢,异步 API | 极快,同步 API |
| 存储容量 | 有限制(Android 6MB) | 无明确限制 |
| 多进程支持 | 不支持 | 支持 |
| 加密 | 不支持 | 支持 AES-256 加密 |
| 安装 | 内置 | npm install react-native-mmkv |
typescript
// AsyncStorage 使用
import AsyncStorage from '@react-native-async-storage/async-storage';
export const storage = {
setItem: async <T>(key: string, value: T): Promise<void> => {
await AsyncStorage.setItem(key, JSON.stringify(value));
},
getItem: async <T>(key: string): Promise<T | null> => {
const value = await AsyncStorage.getItem(key);
return value ? JSON.parse(value) : null;
},
removeItem: async (key: string): Promise<void> => {
await AsyncStorage.removeItem(key);
},
clear: async (): Promise<void> => {
await AsyncStorage.clear();
},
};
// MMKV 使用(推荐)
import { MMKV } from 'react-native-mmkv';
export const mmkvStorage = new MMKV({
id: 'app-storage',
encryptionKey: 'encryption-key', // AES-256 加密
});
export const secureStorage = {
setItem: <T>(key: string, value: T): void => {
mmkvStorage.set(key, JSON.stringify(value));
},
getItem: <T>(key: string): T | null => {
const value = mmkvStorage.getString(key);
return value ? JSON.parse(value) : null;
},
removeItem: (key: string): void => {
mmkvStorage.delete(key);
},
clearAll: (): void => {
mmkvStorage.clearAll();
},
};redux-persist
bash
npm install redux-persisttypescript
import { combineReducers, configureStore } from '@reduxjs/toolkit';
import { persistStore, persistReducer, FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER } from 'redux-persist';
import { MMKV } from 'react-native-mmkv';
import authReducer from './slices/authSlice';
import cartReducer from './slices/cartSlice';
const mmkv = new MMKV();
// 自定义 MMKV 存储引擎
const mmkvStorage = {
getItem: (key: string): Promise<string | null> => {
const value = mmkv.getString(key);
return Promise.resolve(value ?? null);
},
setItem: (key: string, value: string): Promise<void> => {
mmkv.set(key, value);
return Promise.resolve();
},
removeItem: (key: string): Promise<void> => {
mmkv.delete(key);
return Promise.resolve();
},
};
const persistConfig = {
key: 'root',
storage: mmkvStorage,
whitelist: ['auth', 'cart'], // 持久化的 reducer
blacklist: ['temporary'], // 不持久化的 reducer
};
const rootReducer = combineReducers({
auth: authReducer,
cart: cartReducer,
// ...
});
const persistedReducer = persistReducer(persistConfig, rootReducer);
export const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}),
});
export const persistor = persistStore(store);安全存储敏感密钥
bash
npm install react-native-keychaintypescript
import * as Keychain from 'react-native-keychain';
// 存储敏感信息(生物识别保护)
export const secureKeychain = {
setCredentials: async (username: string, password: string): Promise<void> => {
await Keychain.setGenericPassword(username, password, {
service: 'com.app.auth',
accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_CURRENT_SET_OR_DEVICE_PASSCODE,
accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
authenticationType: Keychain.AUTHENTICATION_TYPE.BIOMETRICS,
});
},
getCredentials: async (): Promise<Keychain.UserCredentials | null> => {
try {
const credentials = await Keychain.getGenericPassword({
service: 'com.app.auth',
authenticationPrompt: { title: '验证身份以访问凭证' },
});
return credentials;
} catch {
return null;
}
},
resetCredentials: async (): Promise<void> => {
await Keychain.resetGenericPassword({ service: 'com.app.auth' });
},
};
// 存储 API Token
export const tokenStorage = {
setAccessToken: async (token: string): Promise<void> => {
await Keychain.setGenericPassword('access_token', token, {
service: 'com.app.token.access',
accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
});
},
getAccessToken: async (): Promise<string | null> => {
try {
const creds = await Keychain.getGenericPassword({ service: 'com.app.token.access' });
return creds?.password ?? null;
} catch {
return null;
}
},
setRefreshToken: async (token: string): Promise<void> => {
await Keychain.setGenericPassword('refresh_token', token, {
service: 'com.app.token.refresh',
accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
});
},
getRefreshToken: async (): Promise<string | null> => {
try {
const creds = await Keychain.getGenericPassword({ service: 'com.app.token.refresh' });
return creds?.password ?? null;
} catch {
return null;
}
},
clearTokens: async (): Promise<void> => {
await Keychain.resetGenericPassword({ service: 'com.app.token.access' });
await Keychain.resetGenericPassword({ service: 'com.app.token.refresh' });
},
};状态管理
Redux Toolkit
bash
npm install @reduxjs/toolkit react-reduxtypescript
// store/slices/authSlice.ts
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import { authApi } from '../../services/api';
interface AuthState {
user: User | null;
token: string | null;
isLoading: boolean;
error: string | null;
}
const initialState: AuthState = {
user: null,
token: null,
isLoading: false,
error: null,
};
// 异步 thunk
export const loginThunk = createAsyncThunk(
'auth/login',
async (credentials: { email: string; password: string }, { rejectWithValue }) => {
try {
const response = await authApi.login(credentials);
return response.data;
} catch (error: any) {
return rejectWithValue(error.message);
}
}
);
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
logout: (state) => {
state.user = null;
state.token = null;
state.error = null;
},
setCredentials: (state, action: PayloadAction<{ user: User; token: string }>) => {
state.user = action.payload.user;
state.token = action.payload.token;
},
},
extraReducers: (builder) => {
builder
.addCase(loginThunk.pending, (state) => {
state.isLoading = true;
state.error = null;
})
.addCase(loginThunk.fulfilled, (state, action) => {
state.isLoading = false;
state.user = action.payload.user;
state.token = action.payload.token;
})
.addCase(loginThunk.rejected, (state, action) => {
state.isLoading = false;
state.error = action.payload as string;
});
},
});
// immer 特性:可以直接修改 state,不需要手动不可变更新
// 因为 Redux Toolkit 内部集成了 immer
export const { logout, setCredentials } = authSlice.actions;
export default authSlice.reducer;typescript
// store/index.ts - configureStore
import { configureStore } from '@reduxjs/toolkit';
import { setupListeners } from '@reduxjs/toolkit/query';
import authReducer from './slices/authSlice';
import cartReducer from './slices/cartSlice';
import { apiSlice } from './apiSlice';
export const store = configureStore({
reducer: {
auth: authReducer,
cart: cartReducer,
[apiSlice.reducerPath]: apiSlice.reducer, // RTK Query
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware()
.concat(apiSlice.middleware),
});
setupListeners(store.dispatch);
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;typescript
// store/apiSlice.ts - RTK Query
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import { RootState } from './index';
export const apiSlice = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({
baseUrl: 'https://api.example.com',
prepareHeaders: (headers, { getState }) => {
const token = (getState() as RootState).auth.token;
if (token) {
headers.set('Authorization', `Bearer ${token}`);
}
return headers;
},
}),
tagTypes: ['User', 'Product', 'Order'],
endpoints: (builder) => ({
// 查询端点
getUsers: builder.query<User[], PaginationParams>({
query: (params) => ({
url: '/users',
params,
}),
providesTags: (result) =>
result
? [...result.map(({ id }) => ({ type: 'User' as const, id })), 'User']
: ['User'],
}),
getUserById: builder.query<User, string>({
query: (id) => `/users/${id}`,
providesTags: (result, error, id) => [{ type: 'User', id }],
}),
// 变更端点
createUser: builder.mutation<User, CreateUserInput>({
query: (data) => ({
url: '/users',
method: 'POST',
body: data,
}),
invalidatesTags: ['User'],
}),
updateUser: builder.mutation<User, { id: string; data: Partial<User> }>({
query: ({ id, data }) => ({
url: `/users/${id}`,
method: 'PUT',
body: data,
}),
// 乐观更新:onQueryStarted
async onQueryStarted({ id, data }, { dispatch, queryFulfilled }) {
const patchResult = dispatch(
apiSlice.util.updateQueryData('getUserById', id, (draft) => {
Object.assign(draft, data);
})
);
try {
await queryFulfilled;
} catch {
patchResult.undo();
}
},
invalidatesTags: (result, error, { id }) => [{ type: 'User', id }],
}),
deleteUser: builder.mutation<void, string>({
query: (id) => ({
url: `/users/${id}`,
method: 'DELETE',
}),
invalidatesTags: ['User'],
}),
}),
});
export const {
useGetUsersQuery,
useGetUserByIdQuery,
useCreateUserMutation,
useUpdateUserMutation,
useDeleteUserMutation,
} = apiSlice;状态管理方案对比
| 特性 | Redux Toolkit | Zustand | MobX | Context API |
|---|---|---|---|---|
| 学习曲线 | 中等 | 低 | 中等 | 低 |
| 样板代码 | 中等 | 极少 | 中等 | 少 |
| 性能 | 好(选择性订阅) | 好 | 好(响应式) | 一般(全体重渲染) |
| TypeScript 支持 | 优秀 | 优秀 | 好 | 一般 |
| 中间件 | 丰富 | 有限 | 有限 | 无 |
| 异步处理 | Thunk/Saga/RTK Query | 内置 | flow | 需自行处理 |
| 适用场景 | 大型应用,复杂状态 | 中小型应用 | 响应式偏好 | 简单状态共享 |
| 调试工具 | Redux DevTools | DevTools | MobX DevTools | React DevTools |
Zustand(轻量状态管理)
typescript
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import { MMKV } from 'react-native-mmkv';
const mmkv = new MMKV();
const zustandMMKVStorage = createJSONStorage(() => ({
getItem: (key: string) => mmkv.getString(key) ?? null,
setItem: (key: string, value: string) => mmkv.set(key, value),
removeItem: (key: string) => mmkv.delete(key),
}));
interface CartStore {
items: CartItem[];
totalAmount: number;
addItem: (item: CartItem) => void;
removeItem: (id: string) => void;
clearCart: () => void;
}
export const useCartStore = create<CartStore>()(
persist(
(set, get) => ({
items: [],
totalAmount: 0,
addItem: (item) =>
set((state) => {
const existing = state.items.find((i) => i.id === item.id);
if (existing) {
return {
items: state.items.map((i) =>
i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
),
totalAmount: state.totalAmount + item.price,
};
}
return {
items: [...state.items, { ...item, quantity: 1 }],
totalAmount: state.totalAmount + item.price,
};
}),
removeItem: (id) =>
set((state) => ({
items: state.items.filter((i) => i.id !== id),
totalAmount: state.totalAmount - (state.items.find((i) => i.id === id)?.price ?? 0),
})),
clearCart: () => set({ items: [], totalAmount: 0 }),
}),
{
name: 'cart-storage',
storage: zustandMMKVStorage,
}
)
);原生模块与打包
原生模块(Native Modules vs Turbo Modules)
| 特性 | Native Modules(传统) | Turbo Modules(新架构) |
|---|---|---|
| 注册方式 | 手动注册到 Package | 自动注册,通过 CodeGen |
| 加载方式 | 应用启动时全部加载 | 懒加载,按需初始化 |
| 通信方式 | Bridge(异步序列化) | JSI(同步直接调用) |
| 类型安全 | 手动保证 | CodeGen 自动生成类型 |
| 性能 | 序列化开销 | 零拷贝,高性能 |
| 兼容性 | RN 0.68 以下 | RN 0.68+(New Architecture) |
iOS Swift 原生模块
swift
// iOS/SwiftNativeModule.swift
import Foundation
@objc(SwiftNativeModule)
class SwiftNativeModule: NSObject {
// 同步方法
@objc
func getDeviceInfo() -> [String: Any] {
return [
"deviceName": UIDevice.current.name,
"systemVersion": UIDevice.current.systemVersion,
"model": UIDevice.current.model,
]
}
// 异步方法 - Callback
@objc
func calculateHash(_ input: String, callback: @escaping RCTResponseSenderBlock) {
DispatchQueue.global().async {
let hash = self.sha256(input)
callback([hash])
}
}
// 异步方法 - Promise
@objc
func fetchData(_ url: String,
resolver resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
guard let requestUrl = URL(string: url) else {
reject("INVALID_URL", "Invalid URL provided", nil)
return
}
let task = URLSession.shared.dataTask(with: requestUrl) { data, response, error in
if let error = error {
reject("NETWORK_ERROR", error.localizedDescription, error)
} else if let data = data {
resolve(String(data: data, encoding: .utf8))
}
}
task.resume()
}
private func sha256(_ input: String) -> String {
// 实现 SHA-256 哈希
return input
}
}objc
// iOS/SwiftNativeModule.m - 桥接头文件
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(SwiftNativeModule, NSObject)
RCT_EXTERN_METHOD(getDeviceInfo: (NSDictionary *)info)
RCT_EXTERN_METHOD(calculateHash: (NSString *)input
callback: (RCTResponseSenderBlock)callback)
RCT_EXTERN_METHOD(fetchData: (NSString *)url
resolver: (RCTPromiseResolveBlock)resolve
rejecter: (RCTPromiseRejectBlock)reject)
@endAndroid Kotlin 原生模块
kotlin
// android/app/src/main/java/com/myapp/nativemodules/KotlinNativeModule.kt
package com.myapp.nativemodules
import com.facebook.react.bridge.*
import android.os.Build
class KotlinNativeModule(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {
@ReactModule(name = "KotlinNativeModule")
class KotlinNativePackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return listOf(KotlinNativeModule(reactContext))
}
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager> {
return emptyList()
}
}
override fun getName(): String = "KotlinNativeModule"
// 同步方法
@ReactMethod(isBlockingSynchronousMethod = true)
fun getDeviceInfo(): WritableMap {
return Arguments.createMap().apply {
putString("deviceName", Build.MODEL)
putString("manufacturer", Build.MANUFACTURER)
putString("osVersion", Build.VERSION.RELEASE)
}
}
// 异步方法 - Promise
@ReactMethod
fun encryptData(data: String, algorithm: String, promise: Promise) {
try {
val encrypted = performEncryption(data, algorithm)
promise.resolve(encrypted)
} catch (e: Exception) {
promise.reject("ENCRYPT_ERROR", e.message, e)
}
}
// 异步方法 - Callback
@ReactMethod
fun readFile(path: String, callback: Callback) {
try {
val content = java.io.File(path).readText()
callback.invoke(null, content)
} catch (e: Exception) {
callback.invoke(e.message, null)
}
}
private fun performEncryption(data: String, algorithm: String): String {
// 实现加密逻辑
return data
}
}第三方原生库
相机
bash
# react-native-camera (维护模式)
npm install react-native-camera
# react-native-vision-camera (推荐)
npm install react-native-vision-cameratypescript
import { Camera, useCameraDevice, useCameraPermission } from 'react-native-vision-camera';
const CameraView: React.FC = () => {
const device = useCameraDevice('back');
const { hasPermission, requestPermission } = useCameraPermission();
useEffect(() => {
if (!hasPermission) {
requestPermission();
}
}, []);
if (!device) return <Text>未检测到相机</Text>;
if (!hasPermission) return <Text>无相机权限</Text>;
return (
<Camera
style={StyleSheet.absoluteFill}
device={device}
isActive={true}
photo={true}
video={true}
audio={false}
/>
);
};地图与定位
bash
npm install react-native-maps
npm install @react-navigation/native
npm install react-native-geolocation-servicetypescript
import MapView, { Marker, PROVIDER_GOOGLE, Region } from 'react-native-maps';
const MapScreen: React.FC = () => {
const [region, setRegion] = useState<Region>({
latitude: 39.9042,
longitude: 116.4074,
latitudeDelta: 0.05,
longitudeDelta: 0.05,
});
return (
<MapView
style={StyleSheet.absoluteFill}
provider={PROVIDER_GOOGLE}
initialRegion={region}
onRegionChangeComplete={setRegion}
showsUserLocation={true}
showsMyLocationButton={true}
>
<Marker
coordinate={{ latitude: 39.9042, longitude: 116.4074 }}
title="北京"
description="首都"
pinColor="#007AFF"
/>
</MapView>
);
};推送通知
bash
npm install @react-native-firebase/app @react-native-firebase/messaging
npm install react-native-push-notificationtypescript
import messaging from '@react-native-firebase/messaging';
import PushNotification from 'react-native-push-notification';
// 初始化推送通知
PushNotification.configure({
onRegister: (token) => {
console.log('推送 Token:', token);
},
onNotification: (notification) => {
console.log('收到通知:', notification);
},
permissions: {
alert: true,
badge: true,
sound: true,
},
popInitialNotification: true,
requestPermissions: true,
});
// Firebase Cloud Messaging
export const fcmService = {
requestPermission: async (): Promise<boolean> => {
const authStatus = await messaging().requestPermission();
return (
authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
authStatus === messaging.AuthorizationStatus.PROVISIONAL
);
},
getFCMToken: async (): Promise<string | null> => {
try {
return await messaging().getToken();
} catch {
return null;
}
},
onMessage: (callback: (message: RemoteMessage) => void) => {
return messaging().onMessage(callback);
},
onBackgroundMessage: (callback: (message: RemoteMessage) => void) => {
messaging().setBackgroundMessageHandler(callback);
},
};WebView
bash
npm install react-native-webviewtypescript
import { WebView, WebViewNavigation } from 'react-native-webview';
const WebViewScreen: React.FC = () => {
const webViewRef = useRef<WebView>(null);
const handleNavigationStateChange = (navState: WebViewNavigation) => {
console.log('当前 URL:', navState.url);
};
return (
<WebView
ref={webViewRef}
source={{ uri: 'https://example.com' }}
style={{ flex: 1 }}
onNavigationStateChange={handleNavigationStateChange}
javaScriptEnabled={true}
domStorageEnabled={true}
startInLoadingState={true}
renderLoading={() => <ActivityIndicator />}
// JS 注入
injectedJavaScript={`
document.body.style.backgroundColor = '#f5f5f5';
true;
`}
// 消息通信
onMessage={(event) => {
console.log('来自 WebView 的消息:', event.nativeEvent.data);
}}
// 白名单限制
originWhitelist={['https://*.example.com', 'https://api.example.com']}
/>
);
};动画(Reanimated)
bash
npm install react-native-reanimatedtypescript
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
withTiming,
withSequence,
Easing,
FadeIn,
FadeOut,
SlideInRight,
} from 'react-native-reanimated';
const ReanimatedDemo: React.FC = () => {
const offset = useSharedValue(0);
const scale = useSharedValue(1);
// 动画样式
const animatedStyles = useAnimatedStyle(() => ({
transform: [
{ translateX: withSpring(offset.value, { damping: 15, stiffness: 100 }) },
{ scale: withSpring(scale.value) },
],
}));
const handlePress = () => {
offset.value = Math.random() * 200 - 100;
scale.value = withSequence(
withTiming(1.2, { duration: 100 }),
withTiming(1, { duration: 100 })
);
};
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
{/* 进入动画 */}
<Animated.View
entering={FadeIn.duration(500).springify()}
exiting={FadeOut}
style={animatedStyles}
>
<TouchableOpacity onPress={handlePress} style={styles.box}>
<Text>Animate Me</Text>
</TouchableOpacity>
</Animated.View>
</View>
);
};打包发布
Metro Bundle 与拆包
typescript
// metro.config.js - 基础配置
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
const defaultConfig = getDefaultConfig(__dirname);
const config = {
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true, // 内联 require,优化启动性能
},
}),
},
resolver: {
// 自定义解析配置
sourceExts: [...defaultConfig.resolver.sourceExts, 'svg'],
},
};
module.exports = mergeConfig(defaultConfig, config);JS Bundle 拆包/分包策略:
bash
# 基础包(common.bundle):react-native、react、navigation 等框架代码
# 业务包(business.bundle):业务代码
# 动态加载包(dynamic.bundle):按需加载的模块
# 生成分包命令示例
npx react-native bundle --platform android --dev false --entry-file index.js --bundle-output ./bundles/common.bundle --assets-dest ./bundles/assetsCodePush 热更新
bash
npm install react-native-code-pushtypescript
import codePush from 'react-native-code-push';
// App.tsx
const App: React.FC = () => {
useEffect(() => {
// 检查更新
codePush.sync({
updateDialog: {
title: '发现新版本',
optionalUpdateMessage: '是否更新到最新版本?',
optionalInstallButtonLabel: '立即更新',
optionalIgnoreButtonLabel: '稍后',
},
installMode: codePush.InstallMode.IMMEDIATE,
});
}, []);
return <RootNavigator />;
};
// 使用 CodePush HOC 包装
export default codePush({
checkFrequency: codePush.CheckFrequency.ON_APP_START,
installMode: codePush.InstallMode.ON_NEXT_RESUME,
})(App);Android 发布
bash
# 生成 release AAB(Android App Bundle)
cd android
./gradlew bundleRelease
# 生成 release APK
./gradlew assembleRelease
# 生成的产物在 android/app/build/outputs/
# AAB: android/app/build/outputs/bundle/release/app-release.aab
# APK: android/app/build/outputs/apk/release/app-release.apktypescript
// android/app/build.gradle - 签名配置
android {
signingConfigs {
release {
storeFile file('keystore/release.keystore')
storePassword System.getenv('STORE_PASSWORD')
keyAlias System.getenv('KEY_ALIAS')
keyPassword System.getenv('KEY_PASSWORD')
}
}
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
}
// 版本管理
def versionMajor = 1
def versionMinor = 0
def versionPatch = 0
android {
defaultConfig {
versionCode versionMajor * 10000 + versionMinor * 100 + versionPatch
versionName "${versionMajor}.${versionMinor}.${versionPatch}"
}
}iOS 发布
bash
# 1. 更新版本号
# 在 Xcode 中更新项目的 Version 和 Build 号
# 或手动修改 Info.plist
# 2. 清理构建缓存
cd ios
xcodebuild clean
# 3. 安装 Pods
pod install
# 4. Archive(归档)
# 在 Xcode 中: Product > Archive
# 或使用命令行:
xcodebuild archive \
-workspace MyReactNativeApp.xcworkspace \
-scheme MyReactNativeApp \
-configuration Release \
-archivePath ./build/MyReactNativeApp.xcarchive
# 5. 导出 ipa
xcodebuild -exportArchive \
-archivePath ./build/MyReactNativeApp.xcarchive \
-exportPath ./build \
-exportOptionsPlist ExportOptions.plistxml
<!-- ExportOptions.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>app-store</string>
<!-- 可选: app-store, ad-hoc, enterprise, development -->
<key>teamID</key>
<string>YOUR_TEAM_ID</string>
<key>uploadSymbols</key>
<true/>
<key>uploadBitcode</key>
<false/>
</dict>
</plist>TestFlight 与 App Store Connect
typescript
// 使用 fastlane 自动化提审流程
// Gemfile
source "https://rubygems.org"
gem "fastlane"
// Fastfile
lane :beta do
build_app(scheme: "MyReactNativeApp")
upload_to_testflight(
skip_waiting_for_build_processing: true,
changelog: "Bug 修复和性能优化"
)
end
lane :release do
build_app(scheme: "MyReactNativeApp")
upload_to_app_store(
skip_metadata: false,
skip_screenshots: true
)
endiOS 发布流程总结:
- 在 Apple Developer Center 创建 App ID 和 Provisioning Profile
- 在 App Store Connect 创建应用记录
- 配置 Xcode 项目签名(Signing & Capabilities)
- 使用 Xcode Archive 或命令行构建
- 通过 Xcode Organizer 或 Application Loader 上传到 App Store Connect
- 在 App Store Connect 设置 TestFlight 内部/外部测试人员
- 提交 App Store 审核
Android 发布流程总结:
- 生成签名密钥(keystore)
- 配置 Gradle 签名配置
- 构建 Release AAB/APK
- 在 Google Play Console 创建应用
- 上传 AAB 到 Google Play Console
- 配置 Store Listing、定价和分发
- 提交审核发布
总结
React Native 跨平台开发需要掌握以下核心能力:
- 架构理解:熟悉 Bridge、JSI、Fabric、TurboModules 等底层机制,理解 New Architecture 的优势和迁移路径
- 组件与布局:掌握核心组件用法、Flexbox 布局系统、StyleSheet 性能优化和平台适配
- 导航路由:熟练使用 React Navigation 实现复杂导航结构,包括认证流、深层链接和类型安全
- 网络与状态:合理选择状态管理方案(Redux Toolkit / Zustand),使用 TanStack Query 管理服务端状态
- 原生交互:理解 Native Modules 和 Turbo Modules 的区别,能够编写和集成原生模块
- 打包发布:掌握 Metro 打包优化、CodePush 热更新、Android/iOS 完整发布流程