把全球最大的移动应用 Shopify 迁移到 React Native 上

早在 2020 年,我们就说过 React Native 是 Shopify 移动端的未来。我们自那时起开始将所有的本地移动应用迁移到 React Native 上。每个应用程序都各不相同,没有能适用于所有的应用程序的迁移方法。因此,我们评估了适合每个应用程序的方法,从中选出了每个应用最适配的方法。

Shopify 构建于一次内部黑客马拉松,最初是为了支持小型母婴店或“周末勇士”,发布之后却异常受欢迎,许多大商家也开始使用,这些大商家每年经手价值数十亿美元的交易。这导致代码库积累了大量的技术债务,无法满足拥有数百个售卖点和数万种产品的大型商家对用户体验的需求。经认真评估,我们发现这些问题无法通过渐进式的改变来解决。因此,我们决定全面重写该应用,即使这样做会对商户造成重大的影响。

但是,我们的旗舰移动应用 Shopify Mobile 非常稳定,足以满足商家的需求。Shopify Mobile 是我们最大的应用程序,在每个平台上有大概 300 个页面,构建它花了六年多的时间,因此对它进行重建会是一个巨大的工程。即使使用 RN 来让生产力翻倍(不能保证),也至少需要三年才能完成重建。而且在重建的这段时间内,不能开发新功能,因此,重写 Shopify Mobile 显然是不可能的。


构建和迁移同时进行

让公司的所有移动团队使用同一个技术栈和工具的优点有很多。我们想让 Shopify Mobile 也能获得共享库、组件和工具等其他应用获得的优势。因此,我们决定在应用程序中逐渐使用 React Native,而非全面重写。

我们的 Mobile Enablement 团队已开始将 React Native 引入代码库,功能团队尽量在 React Native 中构建新功能,同时根据需要将现有的代码库迁移到 React Native 中。

这种方法在一段时间内运行得不错。因为不了解 React Native,所以我们可以学习和尝试不同的应用架构方法。这也可以避免在构建大量功能的同时产生技术债务,因为我们在 React Native 方面的专业技术水平远远低于原生技术水平。

但这种方法有缺点:

  1. 迁移工作极其缓慢,需要 4-5 年时间才能完成;
  2. 对设计系统的任何改变都必须进行三次—— 一次在原生 Android 中,一次在原生 iOS 中,还有一次在 React Native 中;
  3. 在迁移完成前,我们必须同时维护三种架构(安卓、iOS 和 RN);
  4. 维护与当前本地代码的互操作性变得非常耗时。

迁移几个月后,这个策略很明显不再适用。我们在提供商家价值方面对其进行了优化,但这样严重限制了迁移工作,拖慢了迁移速度,且让迁移变得不稳定,因此,我们必须重新思考如何继续。

目前的状况已与刚开始大不相同。我们已有使用 React Native 构建复杂功能的坚实基础,还有工程、产品和用户体验领导层的全力支持,而且团队中大多数开发者都同意迁移到 React Native。

评估之后,我们决定采用一种我们称之为“迭代移植”的方法,开始在 React Native 中构建所有新功能,同时迁移现有功能。


培训团队全员

这个计划的实施首先需要让团队中的所有工程师都学会如何编写 React Native 代码。他们都有丰富的使用 Kotlin 和 Swift 构建移动应用的经验,但 React Native 对他们中的许多人来说是一个全新的技术栈。

为了让所有人都能轻松地完成这一过渡,我们与学习和发展团队合作,创建了一个名为 RN 加速器的内部培训计划。

该计划主要培训在 Shopify Mobile 中运送 React Native 代码的所有内容,主要包括下列 5 个主题:

  1. Javascript 和 Typescript
  2. React
  3. React Native
  4. 用于 Shopify Mobile 的 React Native
  5. 高级工具和技术

我们以这种方式划分项目,团队成员就可以跳过已经知道的技术,花更多的时间学习其余内容。我们会跟踪团队成员在项目中的学习进展,给完成培训的成员颁发礼品。通过这个方法,所有工程师很快就完成培训,开始将 React Native 代码投入生产。


谁?什么内容?什么时间?——确定要迁移的内容并进行优先排序

我们想确定所有需迁移到 React Native 的领域,以便计划工作和衡量进度。为此,我们写了一个自定义脚本,查看了 iOS 和 Android 代码,确定了所有需要迁移的源文件。

然后,将这些文件填入电子表格,按照功能和子团队分类。我们还添加了表示复杂性和状态的列。每个子团队都有明确的迁移范围。有几个领域不属于任何团队,所以我们为其确定了新的负责人。

迁移标准

有了要迁移的领域清单后,接下来要确定优先次序。我们使用了一个粗略的优先级框架(基于 RICE 评分模型),确保合理地进行优先级排序,并在所有子团队中保持一致。对于清单上的每个项目,我们确定其:

  1. 覆盖率(Reach):每月使用该功能的活跃用户的数量;
  2. 影响(Impact):该功能在质量或数量上的影响(1-5,1 为最低影响);
  3. 信心(Confidence):你对覆盖范围和影响的信心如何(百分比);
  4. 工作量(Effort):迁移此功能所需的工作量(多少人,多少周)。

RICE 评分的计算方法是:

(覆盖率 x 影响 x 信心)/工作量= RICE 得分

我们用这个分数对功能和组件进行堆叠排序,在各个开发周期内安排时间进行迁移,并及时在电子表格中更新状态。

迁移符合条件的领域时,我们会确保没有遗漏依赖项,且按照迁移(带有功能标签)、测试和运送生产的程序进行迁移。我们对每个迁移的功能进行为期两周的监控,确保其稳定运行,然后再删除该功能的本地版本并去除功能标签,最后将该功能标记为迁移完成。

在执行过程中优化

把页面迁移到 RN 时,我们会尽量寻找改善用户体验的可能性,这样既不会降低商家创新速度,又能将应用迁移到 React Native 上。折扣列表就是一个很好的例子。我们想让搜索方式现代化,使其与应用程序中的其他页面相似,因此我们创建了一个新的组件来渲染列表和过滤器,以 React Native 迁移为契机,应用了新设计。

截图比较了之前的本地折扣清单和全新的 RN 版本

当然,并不是每个页面都能改进,我们创建了一个决策流程,用以确定可以优先考虑的改进,以及哪些团队将负责这些改进。决策的内容主要是改进的类型(例如,故障修复、窗口崩溃或修改 RN 的本地组件)和哪个团队更有能力和带宽来负责这个问题。如果我们无法就解决方案达到一致,或者没有找到合适的团队来负责这个问题,就需要一个项目提案来进一步探索改进的可能性。


从哪儿开始迁移?

我们无法优化不能测量的东西,因此我们的第一步是绘制所有的页面并创建一个仪表板,我们可以使用仪表板轻松地跟踪迁移进度。

用于跟踪迁移进度的仪表板

现在我们只需要选择从哪儿开始迁移。我们考虑过从较小的页面开始,因为这些页面代码少、影响小。但经过讨论和原型设计后,我们决定先考虑影响大的页面,如应用程序的根屏幕。

Shopify Mobile 中基于标签的主要导航界面是用 Kotlin 和 Swift 编写的。为了增加 Shopify Mobile 内的 React Native 开发,必须传输更多的领域。为解除开发人员扩展和修改 Shopify Mobile 的障碍,应用程序需要早一些支持 React Native——在登录后的根屏幕。

迁移根目录屏幕能进一步增加应用程序中的 React Native 页面的数量。这将使 Shopify Mobile 从应用开始到功能屏幕完全使用 React Native,我们也能在应用启动时在 React Native 上进行全局配置,不用等待第一个 React 功能的启动。

Shopify 移动应用程序的根屏幕


迁移的好处

我们做这个项目有明确的目标,除此之外,这个项目还有下列好处。

减少 iOS 和 Android 之间的实施差异

我们一直知道 iOS 和 Android 上的一些业务逻辑是不同的,在传输页面的时候,我们发现这种不同比预期的要多,比如渲染产品字幕的方式、显示某些组件需要检查的权限、在 GraphQL 中查询数据的字段等等。把所有这些统一将简化之后新功能的实现。

人多力量大

iOS 和 Android 开发者一起合作,开发功能、批准 PR 等工作的开发者总数增加了一倍,加快了新功能的开发。另外,这降低了网页开发者为 Shopify Mobile 做贡献的难度, 这将增加为该应用做贡献的开发者数量。

代码更简单

从 iOS 和 Android 中的 Imperative UI 实现到 Declarative UI,代码变得更加简单。另外,我们从头开始写这个项目的过程中,更有处理逻辑问题的意识,删除了从 GraphQL 查询的不必要的变量,清理了旧的实验,并修复一些技术债务。


项目现状

如果你现在打开 Shopify 移动应用,四个根屏幕都已经在 React Native 中了!那么,考大家一下,下面视频中的屏幕哪一个是原生的,哪一个是 React Native?

https://cdn.shopify.com/videos/c/o/v/6aee86d037ad411ea7fe2f56ec87f4a6.mp4

Shopify 移动应用的视频 demo(左边是 React Native,右边是 native。你猜对了吗?

很难回答正确吧!说实话,我也搞不清楚!这也是项目的主要目标之一。一些用户能注意到变化,通常是更好的变化,因为我们也会在一些屏幕上做一些改进,但大多数屏幕的外观和感觉都是一样的。


可能会遇到的挑战

迁移根屏幕花了大约四个月。其中大部分是准备必要的基础设施,这是一项非常值得的投入,因为它最终将用于其他所有页面的传输。Shopify 也在大力投资 React Native 的开源框架,例如,我们内部也在使用这些框架:

过程中有些挑战是我们已经预料到的,有些挑战是在项目进行过程中出现的。以下是我们从这些挑战中学到的经验和教训。

原生路由

在迁移根屏幕时,我们需要打开 React Native 内部屏幕的方法。通常这些屏幕会直接在本地的视图控制器和片段中打开。我们必须建立一种从 RN 打开它们的方法,用一个自定义的名字,在 Android/iOS 中实现它们。我们称这些为原生路由。通过自定义实现,我们在 React Native 中有了一个导航器,允许我们导航到本地屏幕,因此,我们可以完全删除支持的视图控制器和片段,尽可能多地删除每个屏幕的特定本地代码。

深度链接支持

深度链接在安卓系统中不是问题,我们直接以模式的方式打开所需的屏幕。但是在 iOS 上,情况就不一样了——我们要建立整个导航栈来进入那个内屏。这意味着我们试图移植的所有根屏幕也是应用程序中所有深度链接的入口。起初,我们试图通过重新创建整个导航栈来遵循旧的行为。问题是,由于其他页面同时被迁移,有一些非常复杂的情况,例如我们会有 RN->Native->Native->RN。因此,我们决定不采用这种解决方案,而是在 iOS 系统上采用安卓的解决方案,即把深层链接作为一个模式打开。我们已经为每一个内部页面实现了 Native Routes,所以这相当简单,很快就可以实现。

安全区

我们刚开始就觉得有难度的另一件事是确定如何正确地实现安全区。我们的第一直觉是将外部组件封装在一个安全区域视图中,但后来我们意识到,对于其中一些组件,特别是对于水平安全区域的支持,我们需要封装每个单元格的内容,而不是单元格本身。这是因为单元格的背景不应该属于安全区,只是里面的内容需要放在安全区。当我们意识到大多数安全区域的修复应该在用户界面组件中完成,事情就简单了。

不正确与正确的安全区配置(注意不正确版本中的灰色填充)


展望未来

现在,我们的根屏已经迁移完毕,大部分必要的基础设施也已到位,迁移的速度还在加快,我们的大部分开发者在这个项目之前并不了解 React Native,所以每天他们都在学习更多的东西,这进一步加快了速度。

在这样的项目中工作是很有挑战性的,因为你最终会与本地实现和 React Native 一起工作,但如果你喜欢编码并且愿意接受新挑战,这就是最适合你的项目。我们还有很长的路要走,但我们想要的 React Native Shopify 近在咫尺了。

Mauricio de Meirelles 是 Shopify 核心移动团队的开发者。



原文作者:Mauricio de Meirelles
原文链接:https://shopify.engineering/migrating-our-largest-mobile-app-to-react-native



推荐阅读
相关专栏
开发者实践
182 文章
本专栏仅用于分享音视频相关的技术文章,与其他开发者和声网 研发团队交流、分享行业前沿技术、资讯。发帖前,请参考「社区发帖指南」,方便您更好的展示所发表的文章和内容。