为什么选择 React Native?
在移动端开发领域,跨平台方案一直备受关注。React Native(RN)凭借其「Learn once, write anywhere」的理念,允许开发者使用 JavaScript 和 React 构建真正的原生应用,同时保持接近原生的性能体验。
本文将以一个待办事项(TodoList)应用为例,带你从零开始完成一个可运行在 iOS 和 Android 上的跨平台应用,并分享实际开发中的最佳实践与常见陷阱。
环境搭建:避开新手坑
1. 安装 Node 与 Watchman
首先确保 Node.js 版本 >= 16。推荐使用 nvm 管理 Node 版本:
nvm install 18
nvm use 18
macOS 用户建议安装 Watchman 以提升文件监听性能:
brew install watchman
2. 安装 React Native CLI
官方推荐使用 npx react-native init 或 Expo。本文选择纯 RN CLI,以便更灵活地集成原生模块。
npx react-native@latest init TodoApp --version 0.72.6
cd TodoApp
> 💡 注意:RN 版本迭代快,建议锁定一个稳定版本(如 0.72.x),避免因版本不兼容导致构建失败。
3. iOS 环境配置
- 安装 Xcode(最新版)并同意许可协议:
sudo xcodebuild -license accept - 安装 CocoaPods:
sudo gem install cocoapods - 进入 ios 目录安装依赖:
cd ios && pod install
4. Android 环境配置
- 安装 JDK 17(RN 0.72 要求)
- 下载 Android Studio,安装 SDK Platform 33 和 Build-Tools 33.0.0
- 配置环境变量:
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/emulator
export PATH=$PATH:$ANDROID_HOME/tools
export PATH=$PATH:$ANDROID_HOME/tools/bin
export PATH=$PATH:$ANDROID_HOME/platform-tools
> ⚠️ 常见坑:Windows 用户需确保 Hyper-V 或 Intel HAXM 已开启,否则模拟器无法启动。
项目结构设计
一个清晰的目录结构能极大提升可维护性。我们采用如下组织方式:
src/
components/ # 可复用 UI 组件
screens/ # 页面组件
navigation/ # 导航配置
store/ # 状态管理
utils/ # 工具函数
services/ # API 服务
App.tsx
核心功能实现
1. 状态管理:使用 Zustand 替代 Redux
对于中小型应用,Zustand 更轻量且易于上手。安装:
npm install zustand
创建 store:
// src/store/todoStore.ts
import { create } from 'zustand';
interface Todo {
id: string;
text: string;
completed: boolean;
}
interface TodoState {
todos: Todo[];
addTodo: (text: string) => void;
toggleTodo: (id: string) => void;
removeTodo: (id: string) => void;
}
export const useTodoStore = create<TodoState>((set) => ({
todos: [],
addTodo: (text) =>
set((state) => ({
todos: [
...state.todos,
{ id: Date.now().toString(), text, completed: false },
],
})),
toggleTodo: (id) =>
set((state) => ({
todos: state.todos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
),
})),
removeTodo: (id) =>
set((state) => ({
todos: state.todos.filter((todo) => todo.id !== id),
})),
}));
2. 页面组件:TodoList 与 AddTodo
安装必要的 UI 库(可选,本文用原生组件):
npm install react-native-vector-icons
npx react-native link react-native-vector-icons
编写主页面:
// src/screens/HomeScreen.tsx
import React, { useState } from 'react';
import {
View,
Text,
TextInput,
TouchableOpacity,
FlatList,
StyleSheet,
KeyboardAvoidingView,
Platform,
} from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons';
import { useTodoStore } from '../store/todoStore';
const HomeScreen = () => {
const [input, setInput] = useState('');
const { todos, addTodo, toggleTodo, removeTodo } = useTodoStore();
const handleAdd = () => {
if (input.trim()) {
addTodo(input.trim());
setInput('');
}
};
return (
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={styles.container}
>
<View style={styles.inputContainer}>
<TextInput
style={styles.input}
placeholder="添加新任务..."
value={input}
onChangeText={setInput}
onSubmitEditing={handleAdd}
/>
<TouchableOpacity style={styles.addButton} onPress={handleAdd}>
<Icon name="add" size={24} color="#fff" />
</TouchableOpacity>
</View>
<FlatList
data={todos}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<View style={styles.todoItem}>
<TouchableOpacity onPress={() => toggleTodo(item.id)}>
<Icon
name={item.completed ? 'checkbox' : 'square-outline'}
size={24}
color={item.completed ? '#4caf50' : '#ccc'}
/>
</TouchableOpacity>
<Text
style={[
styles.todoText,
item.completed && styles.completedText,
]}
>
{item.text}
</Text>
<TouchableOpacity onPress={() => removeTodo(item.id)}>
<Icon name="trash" size={24} color="#f44336" />
</TouchableOpacity>
</View>
)}
/>
</KeyboardAvoidingView>
);
};
const styles = StyleSheet.create({
container: { flex: 1, padding: 16, backgroundColor: '#f5f5f5' },
inputContainer: { flexDirection: 'row', marginBottom: 16 },
input: {
flex: 1,
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 8,
paddingHorizontal: 12,
height: 48,
backgroundColor: '#fff',
},
addButton: {
width: 48,
height: 48,
backgroundColor: '#2196f3',
borderRadius: 8,
justifyContent: 'center',
alignItems: 'center',
marginLeft: 8,
},
todoItem: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#fff',
padding: 16,
borderRadius: 8,
marginBottom: 8,
},
todoText: { flex: 1, marginHorizontal: 12, fontSize: 16 },
completedText: { textDecorationLine: 'line-through', color: '#aaa' },
});
export default HomeScreen;
3. 导航:React Navigation v6
安装依赖:
npm install @react-navigation/native @react-navigation/native-stack
npm install react-native-screens react-native-safe-area-context
配置导航:
// App.tsx
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeScreen from './src/screens/HomeScreen';
const Stack = createNativeStackNavigator();
const App = () => (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} options={{ title: '待办事项' }} />
</Stack.Navigator>
</NavigationContainer>
);
export default App;
性能优化最佳实践
1. 使用 FlatList 的优化属性
对于长列表,务必添加 keyExtractor、getItemLayout 和 removeClippedSubviews:
<FlatList
data={todos}
keyExtractor={(item) => item.id}
getItemLayout={(_, index) => ({ length: 72, offset: 72 * index, index })}
removeClippedSubviews={true}
initialNumToRender={10}
maxToRenderPerBatch={10}
windowSize={5}
/>
2. 避免内联函数与对象
在 render 中创建新函数或对象会导致子组件不必要的重渲染。使用 useCallback 和 useMemo:
const renderItem = useCallback(({ item }) => (
<TodoItem item={item} onToggle={toggleTodo} onRemove={removeTodo} />
), [toggleTodo, removeTodo]);
3. 图片优化
使用 react-native-fast-image 替代默认 Image 组件,支持缓存和优先级。
原生模块调用:获取设备信息
有时需要访问原生 API。以获取电池电量为例,安装社区模块:
npm install react-native-device-info
cd ios && pod install
使用:
import DeviceInfo from 'react-native-device-info';
const batteryLevel = await DeviceInfo.getBatteryLevel();
console.log(`当前电量:${(batteryLevel * 100).toFixed(0)}%`);
> 💡 提示:RN 0.72 支持 TurboModules,但社区模块可能尚未适配。若遇到编译错误,可暂时降级使用旧架构。
双端打包与发布
Android 打包
- 生成签名密钥:
keytool -genkeypair -v -storetype PKCS12 -keystore my-release-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000
- 配置
android/gradle.properties:
MYAPP_UPLOAD_STORE_FILE=my-release-key.keystore
MYAPP_UPLOAD_KEY_ALIAS=my-key-alias
MYAPP_UPLOAD_STORE_PASSWORD=*****
MYAPP_UPLOAD_KEY_PASSWORD=*****
- 在
android/app/build.gradle中配置签名并执行:
cd android && ./gradlew assembleRelease
生成的 APK 位于 android/app/build/outputs/apk/release/。
iOS 打包
- 在 Xcode 中配置签名与证书。
- 选择 Generic iOS Device,执行 Product > Archive。
- 通过 Organizer 上传到 App Store Connect。
> ⚠️ 注意:iOS 打包必须使用 macOS,且需要付费开发者账号($99/年)。
常见踩坑与解决方案
- Metro 缓存问题:修改代码后不生效,尝试
npx react-native start --reset-cache。 - Android 9+ 网络请求失败:需在
AndroidManifest.xml中添加android:usesCleartextTraffic="true"或使用 HTTPS。 - 键盘遮挡输入框:使用
KeyboardAvoidingView并设置合适的behavior。 - iOS 安全区域:使用
SafeAreaView包裹页面内容。 - Hermes 引擎:RN 0.70+ 默认启用 Hermes,可显著提升启动速度,但需注意部分库兼容性。
总结与下一步
通过本文,你已完成一个具备增删改查、状态管理和导航的跨平台应用,并掌握了性能优化与发布流程。React Native 生态仍在快速演进,建议进一步探索:
- 新架构(Fabric & TurboModules):了解如何迁移以获得更好性能。
- Expo 生态:简化开发与部署流程。
- React Native Web:复用代码到 Web 端。
- 测试:使用 Jest 和 Detox 编写单元与端到端测试。
跨平台开发不是银弹,但 React Native 在开发效率与用户体验之间取得了良好平衡。希望本文能帮助你少走弯路,快速交付高质量应用。