从零开始,用React Native搭建我的第一个APP
引言:为什么选择React Native?

去年我加入了一家初创公司,负责快速开发一款跨平台的移动应用,目标是在三个月内推出iOS和Android版本。作为前端出身的我,虽然有过一些原生开发的经验,但面对时间紧迫、人手有限的情况,我们最终决定采用React Native。
选择它的理由很直接:一次编码,两端运行。而且团队中大部分成员对React都有一定了解,学习成本相对较低。不过说实话,在真正上手之前,我对它的实际表现还是有些顾虑:比如性能、兼容性、与原生的交互是否顺畅。但现实没有给我们太多犹豫的时间,我们立刻开始了项目的搭建。
这篇文章就记录了我第一次使用React Native构建完整APP的经历:从项目结构到代码实现,再到踩坑经历和发布上线的心得。
背景介绍:我们的项目是什么?

我们做的是一个任务管理工具类App,用户可以通过创建待办事项、设置优先级、设定提醒等方式来提高工作效率。主要功能包括:
- 用户登录/注册
- 创建、编辑、删除任务
- 日历视图展示任务
- 基于本地通知的提醒
- 离线支持(SQLite存储)
- 适配深色模式和屏幕旋转
技术栈方面,后端用了Node.js + Express提供RESTful API,数据持久化层是PostgreSQL,移动端选择React Native + Expo(后期转成Eject以定制Native模块)。
遇到的挑战:从想法到落地并不容易

刚上手的时候,我遇到的第一个问题是:“React Native到底应该怎么组织项目结构?”这听起来很简单,但在网上搜索时你会发现各种各样的做法,有的推荐按Feature划分目录,有的推崇Redux + Ducks模式,还有些是基于容器组件/展示组件分离的架构。
我尝试了几种方式后发现:保持简洁清晰才是王道,尤其是在中小型项目中,过度设计反而会带来维护负担。
另一个难点是性能优化和UI渲染效率。由于这个App有大量列表和动态内容更新,如果不注意组件重渲染或列表滚动优化,很容易卡顿。尤其在低端设备上,体验非常差。
此外,还有一些特定平台的问题需要处理:
- iOS上的状态栏高度问题
- Android上的权限申请(特别是通知权限)
- 屏幕旋转时布局错乱
- 打包后的体积过大
这些问题都需要逐一解决,并且需要兼顾两个平台的一致性。
解决方案:一步步搭建你的React Native APP


技术选型决策
在项目初期,我们选择了Expo作为启动框架,因为它提供了很多开箱即用的功能,比如摄像头、相册、Push通知等插件,可以大大加快原型开发速度。
小贴士:如果你只是做一个小型PWA或者不涉及复杂原生逻辑的APP,建议一开始就用Expo;但如果需要深度定制Native模块,建议提前考虑是否需要“脱壳”(eject)
我们最终选择了中期阶段脱壳的方式,这样既保留了Expo的便利性,又能在后期接入更多定制化的原生能力。
项目结构规划
这是我最终采用的目录结构(简化版):
/src
├── assets # 图片、字体资源
├── components # 可复用的组件
├── screens # 页面组件
├── services # API请求服务
├── hooks # 自定义Hook
├── store # Redux相关(如果使用)
├── utils # 工具函数
└── navigation # 导航配置

这种结构的好处是便于团队协作和维护,每个模块职责明确。比如screens文件夹里只放页面组件,components里放可复用的小部件。
核心模块划分
- 导航系统:使用React Navigation(Stack + Tab)
- 网络通信:封装了统一的API请求库,拦截错误码
- 状态管理:前期用Context API+useReducer过渡,后期改为Redux Toolkit
- 本地存储:采用SQLite进行离线数据缓存(通过expo-sqlite)
- 通知系统:使用Expo Notifications插件
开发流程实践
整个开发过程中,我们坚持几个原则:
- 尽量使用纯JS组件避免不必要的Native依赖
- 封装通用功能为Hooks,减少重复代码
- 早期模拟接口数据,方便并行开发
- 使用TypeScript提升代码健壮性
代码实践:关键模块示例
下面我会分享几个核心部分的代码片段,帮助你更快理解整体结构。
1. 初始化App结构
// App.tsx
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import MainStackNavigator from './src/navigation/MainStackNavigator';
const App = () => {
return (
<NavigationContainer>
<MainStackNavigator />
</NavigationContainer>
);
};
export default App;
2. 导航结构定义(Stack)
// src/navigation/MainStackNavigator.tsx
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import HomeScreen from '../screens/HomeScreen';
import TaskDetailScreen from '../screens/TaskDetailScreen';
const Stack = createStackNavigator();
const MainStackNavigator = () => {
return (
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="TaskDetail" component={TaskDetailScreen} />
</Stack.Navigator>
);
};
export default MainStackNavigator;
3. 接口请求封装示例
// src/services/api.ts
import axios from 'axios';
const apiClient = axios.create({
baseURL: process.env.API_BASE_URL,
timeout: 10000,
});
apiClient.interceptors.response.use(
response => response,
error => {
if (error.response?.status === 401) {
// 触发重新登录逻辑
}
return Promise.reject(error);
}
);
export const fetchTasks = async () => {
const res = await apiClient.get('/tasks');
return res.data;
};
4. 本地数据库操作(SQLite)
// src/services/database.ts
import * as SQLite from 'expo-sqlite';
const db = SQLite.openDatabase('task.db');
export const initDB = () => {
db.transaction(tx => {
tx.executeSql(
`CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY NOT NULL,
title TEXT,
completed BOOLEAN
);`
);
});
};
export const getOfflineTasks = () => {
return new Promise((resolve, reject) => {
db.transaction(tx => {
tx.executeSql(
'SELECT * FROM tasks;',
[],
(_, result) => resolve(result.rows._array),
(_, err) => reject(err)
);
});
});
};
踩坑经验分享:那些让我熬夜的地方
1. 列表性能优化
我们在首页用了一个FlatList来展示任务列表。一开始没做任何优化,每次更新数据就会导致整个列表重新渲染,滑动非常卡顿。
后来加了这些改进:
- 使用
keyExtractor指定唯一Key - 组件加上
React.memo()避免不必要的重渲染 - 数据更新时尽量用Immutable方式避免引用污染
- 对长列表进行了虚拟滚动优化(cell高度一致)
2. 屏幕旋转适配
某些页面在旋转屏幕后会出现布局错乱,尤其是Flex布局。我们用到了React Native的Dimensions模块监听变化,然后动态调整样式。
import { Dimensions } from 'react-native';
const onOrientationChange = () => {
const { width, height } = Dimensions.get('window');
setOrientation(width > height ? 'landscape' : 'portrait');
};
同时,在关键组件中使用条件判断控制显示效果,保证视觉一致性。
3. Push通知在iOS上无法弹出
调试了好几天才发现是因为苹果的APNs证书配置错误,而且必须手动开启推送权限(Expo默认不会自动请求)。这个问题在真机测试前根本看不出来。
解决方法是在入口处主动触发权限申请:
import * as Notifications from 'expo-notifications';
Notifications.requestPermissionsAsync();
另外还要确保后台服务器推送时携带正确的Token格式。
4. 安卓权限申请被拒绝
Android 6.0之后的所有危险权限都必须动态申请。我们在拍照上传头像时遇到权限被用户拒绝后无响应的问题。
于是写了个封装函数:
async function requestCameraPermission() {
const { status } = await ImagePicker.requestCameraPermissionsAsync();
if (status !== 'granted') {
Alert.alert('权限不足', '请开启相机访问权限');
return false;
}
return true;
}
5. 发布打包体积过大
首次使用Expo打包的产物竟然达到了48MB!远远超过苹果和谷歌商店推荐的限制。经过分析发现主要是第三方库和内置的一些冗余资产占用了空间。
我们采取了以下措施:
- 移除不必要的插件(如未使用的传感器)
- 替换部分UI库(如Ant Design Mobile RN版太大,改成了React Native Paper)
- 压缩图片资源,用矢量图标替代PNG
- 使用Proguard优化Android包体(仅适用于ejected项目)
最终包体缩小到27MB左右,符合主流审核标准。
效果总结:项目成果与收益
经过三个月的努力,这款任务管理App成功上线iOS和Android平台,首月下载量突破1万次,用户反馈良好。有几个关键指标值得参考:
| 指标 | 数值 |
|---|---|
| 平均启动时间 | 1.8秒 |
| 次留率(第3天) | 35% |
| 性能评分(Google Play Console) | A级 |
| 包体大小(Android/iOS) | 27MB / 32MB |
最让我们欣慰的是,开发过程中积累的React Native经验和架构设计思维,成为后续多个项目的基石。团队内部也逐渐形成了自己的RN最佳实践指南。
经验分享:给新手的几点建议
结合自己一路走来的经历,我想送给刚开始学习React Native的同学几点建议:
1. 学习路线要循序渐进
不要一开始就想搞多复杂的组件。先掌握基础三板斧:
- JSX语法
- State 和 Props
- 生命周期和Hooks
- 简单的导航跳转
然后逐步引入异步处理、本地存储、动画等内容。每一步都写点小Demo练手。
2. 实践比看文档更重要
文档再详细,不如亲手敲一遍代码来得实在。可以试着用React Native实现一个天气预报、记账本或Todo List,功能虽小但五脏俱全,是最好的练习材料。
3. 多关注平台差异
虽然React Native号称“Write once, run anywhere”,但实际开发时你会发现两个平台在细节上有不少差异,比如状态栏颜色、键盘行为、触摸反馈等。最好一开始就做好Platform判断,或者使用社区推荐的适配库(如react-native-safe-area-context)。
4. 重视性能与用户体验
性能问题往往是看不见摸不着的。建议尽早使用React DevTools Profiler和React Native Debugger工具进行调优。特别是在使用大量交互和动画时,更要小心内存泄漏和过度绘制。
5. 不要回避原生模块
当你遇到性能瓶颈或者需要用到专有硬件功能时(如指纹识别、AR),不可避免地要接触原生代码。这时候不要害怕去学Objective-C或Java。也可以使用现有的开源库,节省时间。
结语:一起拥抱跨端未来
React Native是一个不断进化的框架,尽管它现在还有很多不完善之处,但它依然是目前最容易上手、生态最完善的跨平台方案之一。
回过头来看,这段旅程虽然磕磕绊绊,但也正是一个个问题的解决,才让我真正理解了React Native的潜力与边界。无论你是前端转型移动端,还是准备入门前端工程师,都强烈建议你动手试一试,亲自感受一下跨端开发的魅力。
愿你在学习React Native的路上少踩坑、多收获!
如果你觉得这篇教程对你有帮助,欢迎在评论区留言讨论,或者分享你自己的React Native入门故事,我们一起成长!

评论 0