React Native 还是 Swift、Kotlin:我们怎么选技术栈
Software Mansion 讲了技术栈决策背后的五个问题和三种方案,并解释了 Gobi 为何一步步地把 React Native 应用迁到原生 iOS 和 Android。
中文
复制

React Native 和原生之间怎么选,很少是一锤定音的决定。合适的技术栈取决于产品要做什么、上线要多快、团队预期它怎么成长。性能、预算、平台能力的可用性、长期维护,这些因素在产品生命周期的每个阶段权重都不一样。
我们用 React Native、Swift、Kotlin 和混合方案做过移动应用,很清楚如何在共享代码的效率与原生开发的专属控制之间取得平衡。
下面讲我们在选型前会问的五个问题、我们从中挑选的三种方案,以及这些答案在 Gobi 身上如何一步步变化。Gobi 是一款发现地点和活动的社交应用,我们从 React Native 迁到了原生 iOS 和 Android。
选型前我们看什么
为了让这些取舍落到实处,我们会依次过五个问题:
- 要交付什么,什么时候交? 我们把本次发布的范围、截止时间和预算,与每种方案所需的工作量放在一起比较;React Native 和 EAS 确实能让分发更轻松,兼容的 JavaScript 和资源更新可以通过热更新下发,但原生层面的改动仍然需要重新构建,估算时要把框架层面的变通做法和预期的迁移一并算进去。
- 当前应用卡在哪里? 我们排查崩溃、卡死、帧率偏低和响应缓慢。在建议迁移之前,先弄清问题出在 UI 渲染、状态管理、数据加载还是后端。关键问题是:这些是一次性的毛病,还是持续存在、意味着架构该改的问题。
- 核心功能是什么,它们怎么运作? 我们看功能的复杂度、数据量,以及频繁的交互或新数据的到达如何引发需要协同的 UI 更新。还要考虑每一帧动画里必须完成多少工作,哪些需要挪到后台线程,才能让交互保持流畅。
- 产品要往哪走? 我们想知道新功能是会提升共享界面的价值,还是反而需要更多原生控制。如果新功能能复用共享的页面和交互模式,React Native 依然有吸引力。分开的 SwiftUI 和 Jetpack Compose 界面,仍然可以通过 Kotlin Multiplatform 共享业务逻辑,或者借助 Swift SDK for Android 复用兼容的 Swift 模块。
- 现在有什么,以后谁来维护? 我们审视现有代码、集成方式和团队经验。对大型 React Native 应用,有针对性的原生开发可能有帮助。小项目如果问题严重,也许值得尽早重构或迁移。两种情况都需要一个团队撑得住的架构。
这些问题帮助我们选择一种契合产品工作负载、发布目标以及团队维护能力的方案。
React Native、原生,还是两者兼用:三种方案
以 React Native 为基础
React Native 让 iOS 和 Android 共享 UI 与应用逻辑,其生态也缩短了从想法到发布的路径。现成的库和 Expo 的 SDK 让常见功能更易实现,Fast Refresh 和 React Native DevTools 则支持快速迭代与调试。借助 Expo 的 EAS,构建与分发变得更简单,EAS Update 还能以 OTA 更新的方式,把兼容的改动下发到 JavaScript 代码和资源。原生层面的改动仍需重新构建。当两个平台有大量共通之处、团队又能从频繁更新中获益时,这种方式尤其合适。
React Native 加原生模块
混合方案把上述工作流与部分用原生代码实现的功能结合起来。完全用原生代码实现的功能可以自行管理数据加载、状态和用户界面,同时对外提供一套有限的命令与结果接口。这种安排适合那些在共享界面之外还配有专门体验的产品,比如交互式地图、媒体处理或自定义手势。每项功能都可以采用适合自己的实现方式,整体组合仍能落在产品的长期架构之内。
完全原生的应用
原生应用可以直接使用平台的 API,以及并发、后台处理和动画方面的工具。混合功能同样可以用到这些。采用完全原生的架构,团队可以在整个应用中统一协调它们,而不必依赖 JavaScript 到原生的集成层。当紧密耦合、要求苛刻的交互占据产品的很大一部分,且团队需要在数据处理、渲染和用户输入的交互方式上保持灵活时,这种方式尤其合适。业务逻辑仍可用 Kotlin Multiplatform 共享,界面则分别用 SwiftUI 和 Jetpack Compose 实现。
我们既承接从零开始搭建新应用的 greenfield 项目,也承接在现有应用上扩展或改造的 brownfield 项目。两者都可以用 React Native、原生 UI,或两者混用;起点决定了我们如何引入并逐步演进这套架构。
有了清晰的架构、定义明确的任务和可靠的检查手段,AI 编码 agent 能让这三种方式的实现与优化都快得多。跨这些技术栈的经验,帮我们判断每种方式在哪里价值最大。Gobi 就是一个例子,说明这种平衡会随产品成长而变化。

同一光谱上的三种方式:React Native、带原生模块的 React Native、全原生。
Gobi 从 React Native 到原生的渐进式迁移
Gobi 是一款让用户在交互式地图上发现、收藏和评价地点与活动的应用,AI 助手会给出建议;结果和详情显示在地图上方的可拖拽面板里,因此用户探索时界面必须始终保持同步。
Gobi 找到 Software Mansion 时,团队正在开发一款 React Native 应用,需要移动端开发方面的专业能力来打磨核心交互、改善响应速度,并支撑后续的产品开发。
在开发过程中,我们发现了相机移动、标记渲染、面板拖拽、滚动和键盘处理方面的问题。导航和搜索有时显得迟滞,帧率下降也让应用达不到团队为 Gobi 设想的流畅 120 FPS 体验。总体而言,它在精致度和响应速度上没达到我们的预期。我们先在现有 React Native 应用内做针对性的原生改进,逐一解决这些问题。
在改进 Gobi 的底部面板时,我们做出了 React Native Bottom Sheet:面板的移动和手势由原生代码处理,同时承载 React Native 内容。这让我们能更精细地控制拖拽面板如何过渡到滚动其内容。这个库是开源的,你可以直接用在自己的 React Native 应用里。
后来我们开发了自己的原生地图模块,在标记、镜头移动和应用专属优化方面获得了更大的自由度。用原生代码处理地图数据与交互后,我们在不减少地图上显示元素数量的前提下,达到了预期的 120 FPS。
到那时,我们已经在 React Native 界面之下维护着一组不断增长的原生模块。每个模块都带着自己的一层桥接:适配器、共享状态,以及两端必须保持一致的行为;而 Gobi 路线图上每一个要求较高的功能,都还得再走一遍同样的流程。权衡之后,维护这座桥、并让后续功能横跨两层来实现,成本高于把现有代码一次性迁到原生。于是我们决定把整个应用迁移到 Swift 和 Kotlin。
迁移分阶段进行。最初,原生内容被嵌在由 React 控制的面板中;之后,原生代码接管了导航和外层容器,而现有的 React Native 组件仍留在原生应用里,直到对应的替代实现就绪。我们把这些内嵌视图称为 React islands。功能和共享状态逐步迁移,每个 island 在其原生版本接管后即被撤下。清晰的计划和 AI 编码 agent 支撑了这次迁移,与此同时新功能仍在持续发布。当不再有功能依赖 React 时,我们移除了它的运行时,完成了向原生 iOS 和 Android 应用的过渡。
并非每个应用都需要迁移
Gobi 只是整个光谱上的一个点。在我们经手的大多数应用里,React Native 仍是基础,原生代码只用在产品真正需要的地方。Filament 是一个仅限邀请的聊天平台,它基于 WebView 的编辑器跟不上需求,于是我们做了一个完全原生的富文本输入框,现已开源为 React Native Enriched。我们还把 Android 的通知处理移到了原生代码,加密消息不必再等整个 JavaScript 运行时启动。应用本身仍留在 React Native 中,iOS、Android、iPad 布局以及用同一套 Metro 配置构建的 Web 版本共用一份代码。针对消息加密和语法高亮的定向优化,把 Android 冷启动从 6–10 秒缩短到约 800 ms,应用体积减少了 30% 以上。
有时候,正确答案就在这个光谱上的另一个位置。SeaPeople 被崩溃和耗电问题困扰时,问题并不出在技术栈上,真正的原因在于后台任务的处理方式,以及一个每秒都重新渲染的界面。在 React Native 内部修复这些问题后,iPhone 13 上的 CPU 占用从 170% 降到 30%,无崩溃率从 95% 提升到约 99%。我们还帮助产品实现了增长:一个两人的团队用两周时间做出了新版 Groups 功能的第一个版本。
Vibecode 则从一开始就走了另一条路。这个产品运行的是用户生成的应用,所以我们用原生 SwiftUI 构建了 iOS 应用,并把 React Native 作为这些应用的受控运行环境嵌入其中。在这里,React Native 能在运行时加载 JavaScript bundle,这项能力本身就是产品特性:每个生成的 Expo 应用都以 JavaScript bundle 的形式发布,由宿主应用拉取、缓存并运行;React Native 的 Element Inspector 则为 AI agent 提供它所编辑的 UI 的结构化信息。它的工作方式很像 Gobi 迁移中的 React islands,只不过这里是长期架构。
我们的结论
你选择的技术应该匹配你打算构建的产品;在决定采用哪种方案时,先看产品的核心功能、发布目标、预期增长和团队情况。
如何为你的应用做决定:
- 选择 React Native,如果你希望跨平台共享界面和逻辑、借助其工具和库生态加快开发测试和发布,并保留随产品成长而扩展应用的灵活性。
- 加入原生集成,如果部分功能需要自定义 UI、平台 API 或有针对性的性能优化,比如渲染、动画和密集的数据处理。应用的其余部分仍可保持共享。
- 考虑完全原生开发,如果应用的大部分都需要直接控制平台,比如繁重的数据处理或频繁、需要协调的交互。对于已有产品,请比较通过针对性改进或迁移来满足相同需求所需的工作量。两种估算都要把测试和持续维护算进去。
- 适合转型时逐步迁移,把现有的 React Native 视图留在原生应用内,直到替代方案就绪。这样用户不会察觉到迁移过程,团队也能继续修 bug、发新功能,我们为 Gobi 做的就是如此。
我们在 Software Mansion 的工作也遵循同样的思路:凭借构建移动应用及其底层库的经验,帮助团队做出技术选型,并在产品成长过程中引导技术迁移。
常见问题
用 React Native 构建应用有哪些好处?
React Native 让你能为 iOS 和 Android 构建原生应用,团队可以跨平台共享 UI 和业务逻辑。各类库、开发者工具以及 Expo 服务都能加快开发速度,支持快速迭代。如果某个功能需要自定义原生代码,可以按需集成 Swift 或 Kotlin 模块与组件,同时不失去共享代码库的灵活性。
React Native 与 Swift、Kotlin 对比:什么时候原生方案更合适?
当自定义交互、持续的数据处理或深度的平台集成是体验的核心时,原生开发更合适。它让团队能直接控制渲染、并发和平台 API,从而优化高负载的工作流。有了清晰的架构和统一的行为约定,AI 编码 agent 可以加速多个平台上的实现与测试。业务逻辑仍可通过 Kotlin Multiplatform 共享,各平台的用户界面则分别用 SwiftUI 和 Jetpack Compose 实现。
可以混用 React Native 和原生代码吗?
可以。原生模块对外暴露原生 API 和逻辑,原生组件对外暴露原生 UI,使用的接口包括 Turbo Native Modules 和 Fabric Native Components。集成的范围可以小到一项能力,也可以大到包含原生数据处理、逻辑和 UI 的完整功能。反过来同样可行:原生应用可以把 React Native 视图或流程作为 React islands 嵌入,Gobi 在迁移期间就是这么做的。这种灵活性让我们可以决定哪些职责继续共享、哪些交给原生代码。
Kotlin Multiplatform 与 React Native 对比:该选哪个?
如果你希望用一套代码库同时承载 UI 和逻辑,并且看重非常成熟的库生态、庞大的社区和 OTA 更新,就选 React Native。共享 UI 在 iOS 和 Android 上都渲染为原生平台组件,Fast Refresh 即时呈现改动,而且由于公开的 React Native 代码数量庞大,AI 编码 agent 对它相当熟悉。
当上述问题指向原生开发、但你仍希望共享代码时,选 Kotlin Multiplatform。共享的 Kotlin 代码在 Android 上编译为 JVM 字节码,在 iOS 上编译为原生机器码,因此共享逻辑以原生速度运行,应用体积也更小。Kotlin 还具备严格的类型安全,并通过协程和调度器支持真正的多线程执行,可以并行运行 CPU 密集型任务。你可以只共享业务逻辑,UI 分别用 SwiftUI 和 Jetpack Compose 编写;也可以连 UI 一起共享,例如使用 Compose Multiplatform。
两者还能配合使用:React Native 应用可以通过原生模块调用 Kotlin Multiplatform 逻辑。
已有应用也能帮上忙吗?
能。我们会先了解你应用目前的状况,无论是原生的(Swift、Kotlin)还是跨平台的(React Native、Flutter、Kotlin Multiplatform),再帮你确定下一步:有针对性的改进、原生集成,或者像 Gobi 那样逐步迁移。迁移与新功能开发同步进行,而不是取代新功能。让你的产品持续向前。聊聊你的应用。
给应用留出成长空间
你的产品会有自己的难题。把计划告诉 Software Mansion,我们会帮你把它变成一条切实可行的路径,无论你是在准备首次发布,还是想让已有应用更进一步。