
我做了三年移动端混合开发从早期盲目跟风选型到后面被线上问题逼着重新审视技术栈最后在 React Native TypeScript Expo 这条组合上稳定下来。如果你正站在同样的岔路口——纠结要不要入 RN 的坑或者已经被 Flutter、原生、各种小程序跨端方案搞得眼花缭乱这篇文章就是为你写的。这个系列会以 [RN TS Expo 移动端混合开发] 为基线从技术选型逻辑讲起一路覆盖开发环境搭建、工程架构、性能优化和实际打包上线环节。本篇先解决最核心的问题为什么是 React Native而不是别的我会把这几年的真实踩坑经历、选型对比和底层判断逻辑全部摊开说绝不只给你一份 RN 更好的口号式结论。1. 先把结论放在前面React Native 适合谁不适合谁1.1 我的业务形态决定了我需要什么很多人在选型时会犯一个致命错误先把所有跨端方案拉出来列一张对比表然后凭技术热度或招聘市场的价格拍板。我一开始也是这样但后来发现真正能导向正确决策的起点是你的业务形态和团队构成。我当时的处境很典型团队不足十人没有专门的原生开发岗位服务器端以 Node.js 为主前端以 React/Next.js 为主但同时要交付 iOS 和 Android 两个 App。这就引出了一个核心矛盾——如果选原生双端开发意味着团队要同时维护两套代码、两套发布节奏、两套 UI 规范而当时的产品原型验证尚未完成经常要在一周内迭代三四个版本。这种情况下双原生几乎是灾难。如果选一个跨平台技术栈需要的能力就非常明确了团队成员必须能快速上手已会的技能不能浪费热更新和快速迭代能力必须强尽可能绕开应用市场上架的等待周期在未来某个阶段仍然能够触及底层的原生能力而不是困在框架的笼子里React Native 恰好卡在这个需求点上。它允许移动端 UI 层用 JavaScript/TypeScript 编写底层仍然通过桥接或新架构的 JSI 层调用原生组件和原生模块。这意味着当你真的遇到了 React Native 做不了的功能时可以直接写原生代码并暴露给 JS 层调用这条路始终是通的。1.2 先搞清楚混合开发这个词的分量混合开发在市面上经常被滥用很多人把它等同于 WebView 套壳。但 RN 的玩法完全不同它不是把网页塞进壳里而是通过 React 的声明式 UI 描述映射到真实的平台原生组件。你在 JS 里写一个View最终渲染给用户的是 Android 的 ViewGroup 或 iOS 的 UIView而不是一个性能天然受限的 WebView 页面。这个导向有两个直接好处一是页面滑动流畅度、启动内存占用这些手感指标远优于 WebView 套壳方案二是在调试工具链上你依然可以享受 Chrome DevTools 或 React DevTools 带来的开发体验而不是像原生开发那样重新建立一整套心智模型。说句实在话很多人对 RN 的第一印象停留在体验一般、包体积大、启动白屏这些抱怨上。这些问题确实存在但大多集中在 RN 旧架构的老版本阶段或者说是工程配置没做到位的结果。新架构推出后Fabric 渲染器和 TurboModule 替换了老旧的 Bridge很多历史痛点已经从根上缓解了。这个问题我会在后面的启动白屏专题里用一整节展开讲。2. 为什么不是 Flutter也不是纯 Web 套壳2.1 Flutter 与 React Native 的底层差异决定了不同走向既然要回答为什么选 React Native绕不开 Flutter 这个最直接的对标。我不打算用一张所谓社区活跃度对比图来搪塞而是尽量把底层差异讲的直白一点。Flutter 走的是自绘引擎路线。它不依赖平台的 OEM 原生组件而是用 Skia 引擎直接在 Canvas 上画出 UI。这意味着 Flutter 几乎不受平台 UI 能力限制每个像素都可以自定义渲染性能在跨端方案里是最稳定的那一档。但代价是你的 UI 和 JavaScript 生态完全隔离业务逻辑用 Dart 编写与前端社区积累的 TypeScript 类型定义、组件库、工具链没有任何直接复用关系。React Native 恰好相反。它依然依赖 iOS 和 Android 的原生组件去做最终渲染因此你能得到接近原生的平台质感缺点也来源于此——平台差异需要时刻留意某些 RN 组件在 Android 和 iOS 上的表现会有细微差异。对于我这个团队来说这个差异很快演变成了决策分岔路如果团队需要最极致的跨端一致性接受用 Dart 重新学一套语言和生态Flutter 没问题。如果团队已经深度绑定 JavaScript/TypeScript 生态并且希望未来能无缝接轨 React Web 业务React Native 的复用价值是无价的。我不否认 Flutter 技术本身优秀但它解决的不是我团队当前的问题。技术选型不是选最强的而是选最能撬动现有资源的。2.2 React Native 在 Web 生态中的杠杆效应React Native 有一个其他跨端方案很难复制的优势它可以和 React Web 共享大部分业务逻辑。如果你的团队本来就是 React 技术栈那 RN 的组件模型、状态管理方案、路由库、网络请求封装几乎都能从现有代码基础上平移或者复用。我在实际项目中做过一个统计一个中等复杂度的订单模块Web 端和 React Native 端可以复用的主要逻辑代码非 UI 层大约接近 60%包括表单校验、数据状态管理、API 接口封装、通用工具函数。这不代表你能一套代码全平台跑但意味着你不需要为移动端单独培养一套开发语言、一套状态管理认知。类型定义可以共享接口返回数据模型可以共享团队之间的沟通成本会肉眼可见地降下来。从成本角度算一笔粗账一个 RN 工程师和一个 Flutter 工程师在基础能力差不多的情况下如果公司已经积累了 React 项目资产前者的上手曲线短得多。而如果公司完全从零开始没有 React 代码也没有前端团队Flutter 的员工培训和 App 性能体验有时反而更平滑。所以你应该理解为什么我的结论是适合谁比哪个好更重要。2.3 那些 Web 套壳方案看起来很美但天花板明显纯 WebView 套壳方案在早期迭代验证阶段确实很香——开发速度快到离谱完全复用响应式网页一套代码行走天下。但这种方案有不可避免的天花板复杂的手势交互比如地图拖拽、图片裁剪、富文本编辑器里的连续手势操作在 WebView 里会频繁遭遇事件传递和渲染性能瓶颈依赖原生能力的场景比如推送、蓝牙、NFC需要额外写桥插件插件的质量和维护状态参差不齐。混合开发的混合二字衡量的恰恰是 Web 渲染与原生能力融合的精确程度。RN 能优雅地做到大部分 UI 交给原生组件小部分复杂功能直接用原生实现而 WebView 套壳的常见问题是 Web 页面占大头原生能力靠 bridge 插件一个接一个地缝补。两者在长线演进上面的潜力差距不是看一天的性能跑分就能看出来的。3. TypeScript 在 React Native 项目里到底有多重要3.1 没有类型约束的 RN重构等于在雷区里裸奔这一节单独把 TypeScript 拎出来讲因为任何跳过 TypeScript 直接上手 RN 的念头都是我强烈不推荐的。RN 组件之间通过 props 传递数据业务层通过 Redux/Zustand 管理全局状态网络层返回复杂嵌套的 JSON 结构这种到处都是数据流动的架构如果全用 JavaScript 默认的 any 类型放行整个项目的隐性成本会高到你不愿意面对。我见过不少 JavaScript 版本 RN 项目早期开发确实快但到了第三个迭代周期需求开始频繁增加和调整 props 结构时一个字段改动往往引发连锁崩溃你根本不知道哪个页面还在依赖一个即将被删除的旧字段。当你看到 TypeError: Cannot read property xxx of undefined 出现在某个用户反馈中排查半天才发现是上一个组件的 props 拼错了对象名那种感觉不是在写代码是在拆盲盒。TypeScript 的静态类型检查把这一整类问题从事后排查提前到了编写阶段。只要组件的 props 接口定义严谨内部状态流转的联合类型约束合理大部分愚蠢的字段错误在编辑器里就会直接标红。3.2 一份完整的 props 类型约定长什么样在 RN 中我通常会为每个组件单独建一个类型区域必要时单独拆成 types.ts 文件。一个比较典型的写法是这样// src/components/OrderCard/types.ts import { ViewStyle, StyleProp } from react-native; export interface OrderItem { id: string; orderNo: string; amount: number; status: pending | paid | shipped | completed | cancelled; createdAt: string; } export interface OrderCardProps { data: OrderItem; onPress?: (order: OrderItem) void; showAmount?: boolean; containerStyle?: StylePropViewStyle; }这个接口一旦写清楚任何调用方都能明确知道orderStatus 不是随便填字符串而是必须从那五个字面量中选择一个。未来如果要增加订单状态TS 编译器会画出所有遗漏的 switch 分支这种确定性能大幅度降低本地测试的人力消耗。3.3 TypeScript 给自己人挖的几个小坑提前说一下第一React Navigation 的路由参数类型建议统一抽到navigation.d.ts里集中管理而不是在每个页面里写零散的useRoute类型断言否则维护成本会迅速膨胀。第二第三方原生模块很多没有自带类型定义这个时候不要直接declare module xxx简单敷衍过去最好自己写一份描述该模块 API 的d.ts不然团队里每个人对模块的理解都会从零开始。第三fetch 或 axios 网络层是类型最容易丢失的重灾区。我建议封装一个泛型 API 方法比如getT(url: string): PromiseT这样后端返回的数据永远自带结构定义而不是你在业务代码里做一堆as any强转。这些经验不是教科书上的规定是我在真实项目里被反复教育出来的。RN 的核心竞争力在于快速迭代而没有类型约束的快速迭代只会在时间维度上累积技术债最终必然反噬开发效率。4. Expo 的价值不是省掉配置而是替你隔绝了大量原生工程噪音4.1 从 Expo Go 到 development build我经历了三个阶段很多 RN 公司还是用裸 RN CLI 起步因为我最初也认为 Expo 是教学玩具后来被配置折磨了无数轮之后我才发现自己对 Expo 的认知停留在它两三年前的状态现在 Expo 体系已经完全不同了。第一个阶段是纯 Expo Go。用手机扫码就能立刻预览不用碰 Xcode、不用配 Android Studio适合原型验证和 Demo 演示五分钟把一个页面跑起来完全不是做梦。第二阶段是 development build。当项目需要引入自定义原生模块或者要开启一些敏感的权限配置时虽然需要自己构建一次原生项目但 Expo 的 prebuild 机制会自动生成ios/和android/原生工程你可以在这份工程里自由修改原生代码同时仍然保留 Expo 的便捷配置抽象。第三阶段是持续集成和分发。Expo Application Services 提供云构建、自动签名、提交商店审核的能力发布流程大大简化。相比之下纯裸 RN 原生配置的 CI/CD 链路每个环节几乎都需要亲力亲为。4.2 我为什么建议小团队优先拥抱 Expo如果你的团队没有专职原生开发进行原生工程维护裸 RN CLI 会让你陷入大量与业务无关的配置泥潭。你以为你在写业务代码实际却花了大半天处理 CocoaPods 版本冲突、Android Gradle 下载失败、签名证书过期。Expo 的价值恰恰在于把这一层从需要完全理解变成只需要理解边界在哪。但这不意味着你完全不需要理解原生——你依然要知道什么时候必须 eject 或使用 config plugin 去注入原生权限、模块和配置。只是这些污染业务重构的底层代码不需要你在项目一启动时就全部掌握。这个边界感对于小团队极其宝贵。这里也顺带提一个常见误区很多人担心用了 Expo 就拿不到原生功能。实际不是这样。Expo 官方 SDK 涵盖相机、定位、推送、文件系统、本地数据库、生物认证等高频能力用expo install统一管理版本天然规避了很多第三方原生模块之间的版本冲突。真正缺失的冷门原生功能你可以通过 expo-dev-client 和 config plugin 体系自己接入并不会把你锁死。4.3 Expo 对 TypeScript 的支持是目前所有 RN 路线中最舒服的如果你创建新项目npx create-expo-app默认模板不但内置 TypeScript 配置还帮你把路径别名、ESLint、格式化工具全部初始化了一遍。对比裸 RN CLI 每次都要手动改tsconfig.json的路径映射补一堆 ESLint 插件Expo 的开箱即用体验真的帮团队省下了至少一个完整工作日的工程模板搭建时间。5. 关于启动白屏问题选 RN 之前就要了解的系统级判断5.1 启动白屏到底是怎么来的React Native 启动白屏是社区高频搜索词也是很多项目在早期阶段最容易遭受质疑的地方。这个问题的本质并不复杂App 启动时原生部分要先完成初始化创建 Application、加载原生模块、启动 JS 运行环境JavaScriptCore 或 Hermes、下载或加载 JS Bundle在这个过程中用户看到的是一个完全空白的窗口也就是俗称的启动白屏。在旧架构下JS Bundle 体积很大时初始化、解析和执行时间会明显拉长白屏持续时间就可能让人烦躁。但这并不代表 React Native 天生有原罪白屏的严重程度与下面几件事高度相关JS Bundle 是否过大未做拆包的初始化源码全都塞在一个主包自然加载慢Hermes 是否启用Hermes 是 Meta 专门为 RN 打造的 JavaScript 引擎其预编译 AOT 机制可以显著缩短启动解析时间原生侧启动屏(Splash Screen)是否能做到无缝衔接很多白屏问题不是启动慢而是启动屏消失后新页面渲染未完成的空窗期5.2 实际优化手段的优先级清单我自己处理过的白屏优化大致按这个优先级推进开启 Hermes这是成本最低、收益最明显的优化项。精简依赖库把仅用于个别页面的大模块做动态按需加载。使用 Metro 的unstable_enablePackageExports或拆分 Bundle 的能力减少首屏初始化代码量。在原生侧改造启动屏让 Splash 页面停留时间覆盖到 RN 首帧渲染完成避免白屏闪跳。如果你使用的是 Expo 的现代版本默认配置已经帮你开启了 Hermes基础体验比几年前要稳很多。剩下的问题更多是应用层加载逻辑的调优。这个主题后续我会专门开一篇来写这里先不展开。5.3 把白屏当成选型可接受的一种工程代价需要强调的是白屏问题不是用 RN 才会遇到。Flutter 有冷启动问题和 Skia 首帧渲染开销原生开发同样有系统进程启动和完整首屏布局时间。问题不在于哪种技术完全没有等待时间而在于你能不能理解它的耗时来源并有能力去优化它。相对于 WebView 套壳方案动不动一个完整网页的加载 JS 执行时间RN 的启动优化空间和确定性通常更好。6. 一套可以复制到下一份工作的选型决策模型6.1 四层过滤网既然已经掰开揉碎讲完了各类方案的利弊我最后送你一套在实际工作中可以直接套用的决策模型选型时一层层过滤基本不会跑偏。第一步列团队技能清单。不要抽象地问团队会什么而是具体到语言栈、状态管理方案、基础设施文化。第二步列产品生命周期要求。MVP 验证期、快速增长期、成熟维护期不同阶段的诉求差异巨大。你可以在 MVP 期用比较轻的方案快速试错但必须确定后期能不能平滑迁移。第三步列原生能力边界。哪些功能需要调用系统 API哪些功能依赖第三方原生 SDK这些依赖的维护方式决定了你对跨端方案原生控制力下限的要求。第四步算人力成本账。选型不是免费的你需要预估团队切换到这套栈的学习成本、第三方库的维护成本、性能问题出现后的排查成本并把它们转化为工时。6.2 一套适用于中小团队的落地方案以我当前团队为例把这个模型倒进去结论如下团队掌握 TypeScript 与 React产品处于快速迭代期每周至少发版一次需要推送、地图、相机、地理位置等原生能力原生人力资源为零后端高峰时段也无法支援移动端最终技术栈稳定在React Native TypeScript Expodevelopment build Hermes 引擎这套栈让前端团队成员在一个晚上就能跑通完整项目两天内开始写业务代码原生侧的定制需求全部通过 config plugin 封装进基础设施层业务代码不感知原生语言差异。如果你问这个决定会永远对吗我的答案是任何技术选型都会过期但决策模型的复用价值远高于单次结论。当未来某一天新的跨端方案出现时你能用同一套框架快速完成评估而不是跟着社区热度随波逐流。7. 系列预告后续你还会看到什么本篇相当于战役前的地图绘制让你先看清整个战场的形态。之后的系列文章我会继续围绕 [RN TS Expo] 逐层深入从环境搭建和首个项目初始化讲起再讨论组件设计模式、Hermes 和性能调优、原生模块接入、CI/CD 发布流程以及上架 iOS App Store 和 Android 商店的具体避坑经验。每一篇都会包含完整可运行的实战代码和我在真实开发中踩过的细节坑。如果你想跟着实战走建议先把本文第一部分和第二部分看懂再在本地把 Expo 跑通接下来的系列你会有更强的共鸣。