news 2026/9/15 5:26:29

Shopify撤离React Native真相:从跨端回迁原生的真实成本

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Shopify撤离React Native真相:从跨端回迁原生的真实成本

去年 Shopify 官宣把移动端主 App 从 React Native 逐步撤回 Swift/Kotlin 的时候,圈子里讨论声很大。有人把这解读成“跨端已死”,也有人觉得这是“大厂终于认清了现实”。但真正从头到尾跟过这类迁移的人,大概率不会说得这么简单——因为从 RN 回到原生这件事,本质上不是把页面换一种写法重写一遍,而是把一个跑在 JavaScript 引擎上的复杂系统,平稳过渡到两套完全不同的原生技术栈上。这个过程中涉及到的包体积控制、启动链路优化、双端能力对齐、基础设施重建、几百人团队的节奏协调,每一环都足以把一个项目拖垮。

我今天想借这个标题聊点实际的:为什么 Shopify 当初会选 RN,后来为什么扛不住,真正回归原生时又是什么地方最容易让人误判。尤其是“有手就行”这个印象,和真实情况差得不是一星半点。

1. Shopify 当年为什么押注 React Native

很多人在复盘的时候会忽略一个基本问题:一家公司做技术选型,很少会只看“技术本身好不好”,更多是看“在当时的规模、团队和时间窗口下,哪个方案最划算”。

1.1 移动端早期的真实局面

Shopify 的核心业务是电商 SaaS。它的商家后台、数据分析、订单管理、店铺装修这些功能,在移动端上的形态非常重,而且迭代频率极高。早期阶段,Shopify 同时维护着 iOS 和 Android 两套原生应用,每上一个新功能,都要写两遍代码,业务逻辑还经常因为两边写法不一致出现偏差。

这种情况在团队很小的时候还能忍受,但电商类 App 的功能链路很长——从登录、商品列表、购物车、结算到售后,任何一个模块的改动都要双端同步,时间成本直接翻倍。正是在这个节点上,React Native 进入了 Shopify 的视野。

1.2 React Native 当时解决的核心痛点

RN 最明显的优势不是“性能比原生好”,而是让业务团队可以共用一套 TypeScript 代码,把 UI 组件和业务逻辑同时跑在 iOS 和 Android 上。对 Shopify 这种业务逻辑复杂、页面数量巨大的电商平台来说,这意味着人力几乎减半,而且新功能上线的时间窗口能压缩不少。

电商场景还有一个特殊需求:运营活动经常要快速上线和调整。虽然 App 原生也能通过热更新机制做远程配置,但 RN 的 JS Bundle 机制让“不发版就能改 UI 和业务”这件事在工程上变得非常顺滑。这一点对促销活动频繁的电商平台是很加分的。

1.3 早期确实跑通了不少场景

根据 Shopify 自己后来公开的复盘内容来看,RN 在其内部的早期应用并不是失败的。大量普通页面、表单流程、营销模块跑在 RN 上,开发效率有实打实的提升。尤其是 JavaScript 生态里的状态管理、UI 组件库,以及和 Web 团队共享代码的可能性,让很多业务线尝到了甜头。

但也正因为跑通的业务越来越多,RN 在 Shopify 内部的使用范围越滚越大。从几个核心页面,扩展到购物、订单、物流等关键路径,再到整个 App 的第一启动屏都开始由 RN 承载。到这一步,很多性能问题和工程治理问题就开始集中爆发了。

2. 从“一次编写到处运行”到“到处调试”:RN 在超大规模 App 扛不住的真实原因

很多人对 RN 的印象还停留在“小应用跑起来挺流畅”的阶段。但 Shopify 遇到的问题,远比这个复杂。当一个 App 的月活达到千万级、业务模块多达几十个、RN 页面和原生页面交错嵌套时,RN 的很多短板会被无限放大。

2.1 启动白屏问题:链路比你想的长得多

先说热词里反复出现的“react native 启动白屏”。这个问题在小型 Demo 里几乎感觉不到,但在真实的大型 App 里几乎是必然出现。

RN 启动链路大致是:App 冷启动 → 原生容器创建 → 加载 JS Bundle → 初始化 JavaScript 引擎 → 执行业务代码 → 渲染首帧。在旧架构下,这一步还涉及到 JavaScriptCore 的初始化、Bridge 的建立、Native Module 的注册,任何一个环节卡住,用户看到的就是白屏。

我实际在低端 Android 设备上见过这个白屏能持续两三秒,尤其是在首启还要同步拉远程 Bundle 或者做 Bundle 校验的时候。Shopify 的 App 里 RN 承载了启动主流程,一旦 Bundle 解析慢或者引擎初始化慢,用户体验掉得特别快。这也是后来他们把冷启动路径逐步原生化的直接原因之一。

2.2 桥接层的心智负担:高频通信是性能黑洞

RN 的旧架构有一个绕不开的设计:JavaScript 线程和原生线程之间所有的通信都要通过 Bridge 进行序列化和反序列化。模块之间数据量小的时候还好说,但电商 App 恰恰是高交互场景——滚动列表的时候要频繁回调位置信息、图片懒加载要通知原生线程、网络请求结果要回传 JS 层。

一旦这些高频调用叠加起来,Bridge 就会成为瓶颈。你会在 Profile 里看到 JS 线程和 Native 线程之间大量的消息排队,帧率掉到肉眼可见的程度。Shopify 的页面里大量使用长列表、图片墙这种重交互组件,这个问题很难靠调优彻底解决。

2.3 双端一致性陷阱:你以为写一遍就够了吗

RN 的口号是 Learn once, write anywhere,但现实是 iOS 和 Android 上运行 JavaScript 引擎的表现并不完全一致。早期 iOS 用的是 JavaScriptCore,Android 在很长一段时间里也内置了 JavaScriptCore,但两边的内存管理、垃圾回收时机、线程模型都不一样。

更麻烦的是,RN 的组件在 iOS 和 Android 上最终映射到的原生视图并不相同。同一个 padding、同一个 fontSize,在两个端渲染出来可能差几个像素。遇到这类问题,你还是得打开原生代码去调,跨端的效率红利就打了折扣。Shopify 这种对 UI 还原度要求很高的商业 App,在这上面投入了大量排查时间。

2.4 原生能力深度集成时的断裂感

电商 App 离不开相机扫码、Face ID/Touch ID、推送、地图、蓝牙、支付等原生能力。RN 虽然提供了丰富的第三方库,但一旦系统升级、权限模型改变、或者某个交互需要深入系统底层,最终还是得回到原生侧去写。

问题在于,当页面逻辑在 JS 层、底层能力在原生层、中间通信靠 Bridge 时,一旦出现问题,排查链路非常痛苦。你要分别看 JS 调用栈、Bridge 消息日志和原生崩溃日志,三个层面的信息往往对不上。这种断裂感,在单一技术栈的 App 里是不存在的。

2.5 不是 RN 不行,是你的规模到了临界点

说到底,我并不是想输出“RN 是坑货”这种片面结论。RN 在中小型应用里依然是一个效率极高的选择。但 Shopify 的问题在于,它把 RN 用在了整个 App 的最核心路径、最复杂的交互场景,同时还要面对成百上千人的协作规模。

当业务多到一定程度,跨端框架节省的那部分人力,会被双端兼容问题、性能调优问题、 Bridge 通信问题、滚动升级问题一点点吞掉。这是一个边际成本递增的过程。很多大厂从 RN 撤退,不是因为他们不会用,而是因为他们规模太大,跨端带来的复杂度已经盖过了收益。

3. 回迁不是重写,而是“器官移植”:Swift/Kotlin 双线改造的真实难度

很多人听到“从 RN 回到原生”的第一反应是:那就把页面用 Swift 和 Kotlin 重写一遍嘛。但如果你在一个正在运行的 App 上做过大规模重构,就会知道这事完全不是这样。

3.1 你接手的不是一个空项目,而是一个不断生长的系统

RN 版本不能直接停掉——几百万用户还在用。你要做的是在保持线上版本正常运行、新功能持续迭代的同时,把页面一点点替换成原生实现。这个过程没有“按个按钮整体切换”这种魔法,只有一段很长的灰度迁移期。

在实际操作中,这种迁移就像给一架正在飞行的飞机换引擎。你不能让飞机停下来,只能拆下一个旧的,装上一个新的,还得保证乘客全程无感。Shopify 的做法是彻底重写而非渐进式混合吗?不是。他们在很长一段时间里都是让 RN 页面和原生页面共享一个 App,通过路由层做分发,按功能和用户群逐步灰度。真正常见的做法,也是类似思路。

3.2 双端并行开发:设计和审核成本直接翻倍

回到原生,看起来是“不用写 JS 了”,实际上你要同时养两支原生团队。iOS 用 Swift,Android 用 Kotlin,两边除了业务逻辑一致,其他全是独立实现。

这就意味着:

  • 每个 API 接口文档要对齐,请求参数、错误码、响应结构要双端一致;
  • 每个页面设计稿要有明确的 iOS 规范和 Android 规范,导航交互、弹窗样式都不能混用;
  • 每个埋点事件要双端核对,否则数据一上来就是脏的;
  • 每次版本发布要协调 iOS 和 Android 的发布时间窗口。

这些工作在 RN 时代大部分是天然统一的,回到原生之后全都要靠流程和代码规范去约束。我在实际项目中经历过类似的双端治理,坦白讲,这是最容易被低估的部分。

3.3 网络层迁移:一个 URLRequest 背后藏着一堆事

热词里出现了“swift urlrequest get”,可见不少人刚开始学 Swift 时都会从网络请求入手。在 Demo 里,一个 URLRequest 也就是几行代码的事。但在 Shopiy 这种规模的 App 里,网络层迁移是整个改造里最核心也是最枯燥的工程之一。

生产环境下的网络请求要考虑什么?请求头统一签名、token 自动刷新、缓存策略、超时重试、弱网降级、接口埋点、错误上报、证书校验、数据解析容错。RN 时代,这些逻辑大多集中在 JavaScript 的网络库和拦截器里。回到原生,你需要分别用 Swift 的 URLSession 体系和 Kotlin 的 OkHttp/Retrofit 体系各重新实现一遍,还要保证行为和之前线上完全一致。

这不是能不能写出来一个 get 请求的问题,而是整个网络基础组件怎么设计和验证的问题。

3.4 构建配置迁移:Kotlin DSL 与 Groovy DSL 的“有手就行”陷阱

另外一个很容易被忽视的坑,是 Android 构建脚本的迁移。热词里也有“build configuration language kotlin dsl 与 groovy dsl 区别”,这条我在实际迁移中深有体会。

很多项目早期的 Gradle 构建脚本是用 Groovy DSL 写的。Groovy 语法松散、动态语言特性强、字符串处理方便,写起来很随意。而 Kotlin DSL 是类型安全的构建脚本,编译期就能发现配置错误,IDE 自动补全也更好。

听起来是不是“有手就行”?换一下语法而已。但真实情况是,一个大型 Android 工程里的 Gradle 脚本可能涉及几十个模块的依赖管理、变体配置、混淆规则、资源优化、插件扩展。Groovy 里一行pluginManagement的动态配置,迁到 Kotlin DSL 里可能就要显式写类型、写 lambda 规范,老项目里很多隐式约定根本没办法自动转换。

我亲手做过一次代价不小的 Gradle 迁移。

如果项目只有几十个依赖,迁移确实低风险,工程上可以很顺利地把build.gradle改成build.gradle.kts,任务差异不算大,很多常见插件也都有现成写法。

但 Shopify 这种量级的 Android 工程,模块数、插件数、定制 Task 数都远超普通项目,而且内部还有自有构建框架和缓存系统,牵一发而动全身。迁移后如果遇到某个民间插件的 NSIS 任务异常,你连报错在哪个 DefaultTask 里都很难定位。

而且构建速度在最开始往往没有提升,反而因为 Kotlin DSL 编译额外的类型检查、首次运行要加载更多类,构建时间会有一定程度的回退。要配合配置缓存反复微调,才能回到原有构建速度甚至更快。

如果只是“会写 Kotlin 代码”就上手 KTS,大概率会卡在一个个配置文件连环报错里。

3.5 不是“重写”,是“重建基础设施”

现在你应该明白了:回迁原生,最关键的不是页面重写,而是整个 App 的基础设施重建。网络层要重写,数据持久化要重写,导航容器要重写,日志监控要重写,Feature Flag 要重写,性能监控要重写,推送和深链处理要重写。

这些基础设施在 RN 时代被封装在跨端层里,大家感受不到它们的存在。一旦切回原生,你必须用两套语言同时把它们全部接起来,才能让业务方在原生页面上继续正常干活。在我看来,这部分工作量至少要占整个回迁项目的一半以上。

4. 真正难的不是 Swift 或 Kotlin 语法,而是你对架构的重新理解

等到 Swift 和 Kotlin 的代码都能跑起来了,另一个层面的问题才会浮出水面:你究竟要构建一个什么样的原生架构?

4.1 SwiftUI 还是 UIKit,Compose 还是 View System

很多回迁团队会在这里纠结很久。以 iOS 为例,SwiftUI 是当前苹果主推的声明式 UI 框架,新项目用 SwiftUI 能省掉很多 UI 代码,但它和 UIKit 在复杂的交互嵌套下仍然有兼容问题。Shopify 的 App 里有大量自定义视图、复杂表格、视频交互模块,这些都是 SwiftUI 当前还不够顺手的地方。

Android 这边也面临同样的问题:Compose 的声明式 UI 效率高,但在超大列表、复杂自定义绘制、辅助功能兼容上,需要补的坑依然不少。别以为“回原生就是用最新的 UI 框架”,大多数情况下你要做的是根据业务场景在传统 View 体系和声明式体系之间做混合,制定一个完整的分层规范。

我自己见过一些团队,为了统一认知强行全面转向声明式 UI,结果在老旧设备上碰到各种兼容问题,最后不得不退回去保留一部分传统视图。这种反复内耗,比一开始就老老实实做技术评估要费钱得多。

4.2 职责分层:不能只满足于“能跑”

在 RN 时代,页面逻辑、状态管理、数据处理都在 JavaScript 层,分层标准相对统一。回到原生后,如果没有在架构层面明确 presentation、domain、data 三层,很容易变成“原生模板套路由”的混乱状态。

我看到很多刚从跨端回迁的项目,原生代码里 activity/fragment 里塞满了网络请求和业务判断。两三个月后,页面一多、人员一流动,这套代码就变成大型泥球。与其说是回到原生,不如说是回到了二十年前的 MVC 大泥团。

架构落地应该是这样:

  • presentation 层只负责渲染和用户交互,不直接碰数据源;
  • domain 层负责核心业务规则,不依赖 UIKit 和 Android framework;
  • data 层封装网络、缓存、数据库实现;
  • 双端各自维护 domain 层,但领域模型定义尽量保持一致。

这样做之后,至少你从 RN 迁移过来的那部分业务逻辑,可以更平滑地在双端对齐,后续修改也不会因为底层实现跳来跳去而崩溃。

4.3 包体积和启动时间:这些指标最终会“算总账”

回迁原生之后,很多团队会发现一个尴尬的事实:App 体积比 RN 时代更大了,启动时间也只是略有改善,并没有想象中那种“秒开”的效果。

原因不复杂。RN 时代虽然有一个 JS 引擎和一个大 Bundle,但很多原生库并没有被拆掉;而回迁原生后,你又把这两套原生 UI 框架、网络库、图片库全部塞了进来,基础包自然更厚。

真正的优化工作量,在架构完成之后才开始:

  • 启动链路裁剪:哪些服务可以懒加载,哪些组件可以不做冷启动初始化;
  • 动态化包管理:按功能模块拆分动态下发;
  • 资源去重和裁剪:移除 RN Bundle 之后,多语言、图片、字体资源全部重理;
  • 首屏渲染优化:提前接入首帧渲染链路,避免被周边初始化任务阻塞。

这些动作在 RN 时代基本没法做。这是回迁后最值得发力的部分。

4.4 团队能力重建,才是最大的隐性成本

还要提醒一句:RN 团队和原生团队对应的技能栈是不同的,不只是代码语言不同,对性能调优、系统机制、内存管理的理解深度也不同。

Shopify 在回迁原生的时候,不可能直接让所有前端工程师第二天就用 SwiftUI 写页面。他们需要重建 iOS 和 Android 两个原生技术梯队,长期维护双端原生架构。这件事在招聘市场上的成本,远高于当初养一个 RN 团队。

5. 如果你也在考虑类似的迁移,这几条实际建议值得看看

很多中小团队看到大厂回迁,也动了“要不要也把跨端项目改成原生”的念头。这里我给几条基于实际经验的原则,你可以对照自己的场景再判断。

5.1 先分清你是“性能瓶颈”还是“团队规模瓶颈”

如果你只是列表滚动掉帧、启动有点慢,先不要急着迁移。优先做性能剖析,定位瓶颈在 Bridge 通信还是在图片加载,或者在接口响应。大量 RN 项目的卡顿问题,其实是资源加载策略和列表复用配置不当,可以通过优化解决,没有必要全盘推翻。

但如果你面临的问题是团队规模过大、跨端代码治理内耗高、双端兼容问题持续消耗主力人力,那么回迁原生才是一个值得考虑的选项。Shopify 是属于明确触碰到了规模边界的大型 App,并不是普通中小应用的参考坐标。

5.2 回迁之前,先把“原生基建”搭起来

别先从核心页面下手。先把网络层、数据层、导航容器、埋点体系、日志体系、Feature Flag 用原生语言搭好,接着在原生的容器里把线上核心路径复刻一遍,再决定哪些页面第一批切换。

基建没搭好的时候就分页面去写原生,大概率会写出一个又一个信息孤岛。

5.3 用灰度迁移代替“大爆炸”重写

最理想的迁移状态是用户无感的。具体做法是:

  • 服务端通过用户特征或随机分组下发新的原生页面;
  • 监控切换后的崩溃率、卡顿率、页面停留时长和业务转化率;
  • 发现问题立刻回滚到旧实现,保证用户影响最小化;
  • 等新页面稳定跑过一段时间后,再扩大灰度范围。

这个思路不仅适合 RN 回原生,也适合任何一次大版本重构。

5.4 迁移期间,新功能不要停摆

如果你真的在做一个长周期的回迁项目,最容易犯的错是让业务方等你。一家正常运营的电商公司,不可能因为你在做技术迁移就停止上新功能。这就要用到一种兼容期的双轨开发模式:RN 的技术栈继续维持住线上版本的迭代,同时原生团队在内部灰度版本上做替换。Shopify 在迁移期间也是这么处理的。

搞不定这种双轨节奏的项目,往往会因为业务压力被迫中断迁移计划,最终搞成“一半 RN 一半原生”的尴尬状态。

6. 关于 Shopify 这次的迁移,我的真实体会

说了这么多,最后还是想分享一些我个人的判断。

第一个体会是:跨端框架没有绝对的优劣,只有适配的规模范围。Shopify 放弃 RN 并不意味着 RN 不能用了,反而说明 RN 的能力边界已经被划得很清楚。对绝大多数几十人团队的 App 来说,RN 依然是一个效率极高的选择。真正危险的是把小项目里积累的经验直接套用到别人千万级 DAU 的 App 上,然后得出“这框架不行”的结论。

第二个体会是:回迁原生真正的成本,不是写 Swift 或 Kotlin,而是你同时要重建两条原生技术线的基础设施,并且要在业务不停的前提下完成平滑切换。这中间牵涉到的团队组织、工程流程、架构设计、性能监控、灰度发布,没有一项是“有手就行”的。

第三个体会是:跨端与原生从来不是二选一的对立面。即使回迁之后,大型 App 里仍然会保留很多跨端解决方案,用于承载短平快的运营活动页面——成本低、上线快这件事,在商业场景里永远是刚需。

我个人一直很佩服能做这种大规模技术迁移的团队,因为他们要对抗的不只是技术难点,还有漫长的迁移期里来自各方的预期压力。用户不会因为你切了原生就自动变多,业务方也不会因为你在做重构就停止提需求。能在这种环境中把项目稳扎稳打地推完,靠的是扎实的架构基本功,以及极强的项目管理能力。

如果你也在经历类似的跨端回迁,或者在考虑要不要做这件事,我的建议是:先别急着写代码,把刚才聊的这些问题一项项想清楚,尤其是你自己的“规模边界”到底在哪。想清楚了,再动手也不迟。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 5:25:30

形态分量分析实战:基于Python的混合信号稀疏分解指南

简介:形态分量分析是一种源于数学形态学的图像处理技术,擅长将复杂图像拆解为若干基本形态单元,适用于医学影像、工业检测和生物图像识别等场景。针对该技术提供的代码包,面向需要快速上手形态学算法的Matlab用户与图像分析初学者…

作者头像 李华
网站建设 2026/9/15 5:24:08

LabVIEW调用UDS安全访问服务VI详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/15 5:23:24

Windows 11上跑通经典ASP新闻系统:IIS配置与部署全指南

简介:此毕业设计资源包围绕ASP基于Web的学校新闻发布系统开发,面向计算机相关专业毕业生及ASP初学者,提供从需求分析、系统设计到编码实现、测试的全流程参考。内容涵盖论文、源代码、开题报告、文献综述与外文翻译,便于对照学习动…

作者头像 李华
网站建设 2026/9/15 5:23:22

微信小程序源码筛选、组件复用与前后端联调实战指南

简介:一套包含123个微信小程序源码的zip压缩包,大小179.32MB,所涉案例覆盖视频、音乐、商城、资讯、工具、游戏等多类常见场景,适合小程序入门者、前端开发者以及需要快速搭建Demo的爱好者参考。资源中既有“芒果TV”“AppleMusic…

作者头像 李华
网站建设 2026/9/15 5:23:11

鸿蒙系统人脸识别门禁验收指南:功能、性能与场景测试全解析

1. 为什么门禁验收不能只在白天按下"开门"就算过先说一个我自己的教训。前年我在华南某园区做一个人脸识别门禁项目,设备端基于鸿蒙系统,一共部署了8台人脸识别门禁一体机,管理服务器一台,底库约1200人。供应商演示那天…

作者头像 李华
网站建设 2026/9/15 5:22:37

Power BI零售销售数据分析实战:从优衣库数据到业务洞察

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华