React Native
React Native 让前端把 React 心智搬到移动端:JS/TS 写业务与 UI 描述,视图仍是系统原生控件(iOS 的 UIKit 与 Android 的原生组件),而不是自绘。示例基于 RN 新架构默认的稳定线编写,需在安装 Node 与 RN/Expo 工具链的环境验证。
运行模型:从桥到新架构
理解 RN 性能与限制的关键是它的线程模型:
- JS 线程:跑你的 JS 逻辑(Hermes 引擎执行);
- UI/原生线程:系统渲染原生视图并执行布局;
- 老架构:JS 与原生之间走异步 JSON 桥,高频调用慢;
- 新架构(Fabric + TurboModule + JSI):JS 直接持有原生对象引用,跨边界调用同步化、渲染由 Fabric 负责——如今是新项目默认。配套 Codegen 根据类型定义自动生成跨边界代码。
推论:不要把重计算塞进 JS 主逻辑(会卡 UI 事件);原生模块的调用在新架构下也要批量收敛。
工程形态:Expo vs 裸工程
| Expo(推荐新项目) | 裸工程(bare workflow) | |
|---|---|---|
| 上手 | 一行命令起项目,云构建/开发客户端 | react-native init,直接持有原生目录 |
| 原生能力 | Config Plugin 声明式接入;需要时 prebuild 弹出 | 自由改原生代码 |
| 升级 | 省心 | 升级 Helper 手动 |
| 适用 | 绝大多数新产品 | 必须深度定制原生 |
推荐路径:默认 Expo;遇到 Expo 生态覆盖不了的原生需求,用 config plugin / 开发构建(dev client),仍然受控。
组件与样式
- 基础组件:
View、Text、ScrollView、FlatList、Pressable、Image; - 样式用
StyleSheet.create;布局是 Yoga(flexbox 的一个实现):- 默认
flexDirection: 'column'(与 Web 的 row 不同); - 没有 Web 的
margin: auto玄学、没有 CSS 级联,样式不继承(Text 样式例外要显式传); - 像素为逻辑单位,
PixelRatio处理高清屏; - 安全区用
SafeAreaView或react-native-safe-area-context;
- 默认
- 平台差异用
Platform.select或.ios/.android后缀文件。
tsx
<FlatList
data={users}
keyExtractor={(u) => String(u.id)}
renderItem={({ item }) => (
<View style={styles.row}>
<Text style={styles.name}>{item.name}</Text>
</View>
)}
/>导航
- 主流:React Navigation(native-stack + bottom-tabs),页面级组件;
- Expo Router(基于 React Navigation 的文件路由,类似 Next.js)适合新项目;
- 深链:配置
linking,让推送/分享直达页面。
状态与数据层
- 状态:同 React 一套(Context/Zustand/Redux Toolkit),服务端缓存可用 TanStack Query;
- 本地键值:
@react-native-async-storage/async-storage(AsyncStorage)或更快的react-native-mmkv(同步、支持加密); - 本地数据库:
expo-sqlite(新架构兼容好)或 WatermelonDB(关系数据 + 同步场景); - 网络:
fetch够用,建议统一封装超时/重试/缓存(见网络层与本地存储);服务端推送用 expo-notifications 或原生 SDK。
原生模块
什么时候写原生模块:系统能力无现成插件、且插件维护不善时。
- 新架构写法:
TurboModule+ Codegen(TypeScript 定义 → 自动生成原生代码);Expo 侧可用expo-modules-core; - 不要为「简单加一行原生」就弹出裸工程——先评估 dev client + config plugin 能否覆盖;
- 权限类能力(相机/定位/相册)优先系统级插件并做好权限文案。
Hermes 与性能
- Hermes 是默认 JS 引擎:启动更快、内存更省,开箱即用;
- 首屏:减少启动期 JS 执行与同步 IO、图片提前尺寸占位、
InteractionManager延后非关键动画; - 列表:
FlatList打开windowSize/getItemLayout或换 FlashList(Shopify)应对超长列表; - 图片:用网络图占位 + 缓存策略,避免加载原图撑爆内存(详见性能与稳定性);
- 监控:Flipper/React Native DevTools 看 JS 线程时间线;真机「JS 线程占用高 = 逻辑层太重」。
发布与升级
- 发布包要打
--release,按平台出产物并签名(见打包、签名与发布); - OTA 热更:CodePush/App 更新 类方案可更新 JS bundle——iOS 允许「不改变行为的资源/脚本热更」有边界,原生代码不可热更,且国内审核另有要求,先读release 页合规红线;
- 版本升级:小版本渐进升,大版本用 Upgrade Helper 对比 diff,升级后跑一遍原生模块回归(Codegen 变化常是隐雷)。
常见坑速查
| 坑 | 现象 | 对策 |
|---|---|---|
| 忘建原生目录 | 新架构项目缺 android/ios | 用 prebuild(Expo)或 react-native init 管理 |
| Flexbox 假设沿用 Web | 排列方向反了、不支持某 CSS | 记住默认 column;按 Yoga 语义写 |
| 长列表全渲染 | 内存涨、卡顿 | FlatList/FlashList + 合理 key |
| 大图不压缩 | 图片 OOM | 缩略图 URL + 缓存库 + 尺寸约束 |
| 样式不继承 | 文本颜色/字体没生效 | 显式传 style 或建复用组件 |
| 同步阻塞 JS 线程 | 卡顿、事件延迟 | 重计算移原生模块或用 InteractionManager 延后 |
| 依赖版本不匹配 | 原生模块编译错 | 固定 RN/expo 版本,升级走官方工具 |
| 深链配置漏 | 推送/链接打不开 | 配 linking + 原生 scheme 声明 |
| 只测 iOS 不测 Android | 双端行为差异(权限/返回键/字体) | 双端回归清单 |
| 忽略 Hermes | 启停明显变慢 | 确认 release 用 Hermes |
| 权限文案缺失 | 上架被拒/用户拒绝 | 权限用途说明写到位 |
| 环境变量泄漏 | 测试地址进生产 | 构建期注入 + 产物校验 |
检查清单
- [ ] 新项目默认 Expo + dev client,确需裸工程才有理由弹出;
- [ ] 列表用 FlatList/FlashList,图片有缓存与尺寸策略;
- [ ] 样式按 Yoga flexbox 语义书写,双端真机回归;
- [ ] 原生能力先找维护良好的插件,自写走 TurboModule + Codegen;
- [ ] release 用 Hermes,首屏启动项无阻塞;
- [ ] 版本升级有计划,Codegen/原生模块回归覆盖;
- [ ] 深链、权限、环境变量在双端验证。
相关链接:技术全景与选型 · Flutter · 网络层与本地存储 · 打包、签名与发布 · 写作规范见客户端开发索引。