不久之前,Airbnb 团队刚刚宣布放弃使用React Native,才过不久,Udacity移动团队最近也宣布从App中移除了使用React Native开发的最后一批功能。再加上6月中旬,Facebook宣布将大规模重构RN,这一系列的事件,让不少正在使用React Native的开发者瑟瑟发抖,陷入了恐慌之中。

在本文中,Udacity团队将告诉大家他们使用React Native的历程以及放弃他们的原因,也希望给一些开发者一些参考和启发,看自己是否适合React Native。

以下来自Udacity移动团队的自述:

Udacity移动团队

Udacity的移动团队分为iOS和Android两个团队。

团队规模

在引入React Native时:

  • 1个iOS开发
  • 2个Android开发者
  • 1个项目经理
  • 1个设计师

现在:

  • 4个iOS开发者
  • 3个Android开发者
  • 1个项目经理
  • 1个设计师

在使用React Native的18个月中,我们的iOS和Android团队规模都有所增长,整个团队由一名项目经理来领导。

期间,我们还经历了向多设计师和多设计范式转型。

开发背景

在引入React Native时,我们iOS团队唯一的开发人员非常乐于使用React Native,这极大丰富了他之前Javascript和Web的开发经验。两位Android开发人员中的一位对Javascript有丰富的经验,而另一个只有很少的Javascript、React或Web经验。

现在,四个iOS开发人员中至少有三个非常乐于使用Javascript和React Native。后来加入团队的其他Android开发人员也只有很少的Javascript或Web经验。

Udacity的App

用途

Udacity的移动app旨在将Udacity的学习体验带到用户的移动设备上。它支持身份验证、内容发现、程序注册(在某些情况还支持支付),以及消费各种类型的学习资料。

Udacity的App也作为新的实验性功能和旨在提升用户整体学习效果的活动的试验场。

代码库的大小

iOS:97,400行(.swift、.h、.m) Android:93,000行(xml、java、kotlin、gradle)

Udacity为什么以及如何采用React Native?

为什么要引入它?

当时,Udacity正准备推出全新的移动端专用功能。我们希望在两个平台上快速进行实验和验证,因此跨平台具有很大的吸引力。

可以总结为以下几点:

  • 跨平台解决方案具有更高的可行性
  • 大多数(2/3开发人员)团队成员非常适应Javascript和Web开发
  • 可以提高开发速度
  • 来自公司之外其他团队的成功案例

Udacity是如何引入的?

React Native的初始特性是在一个单独的GitHub代码库中构建的,然后作为git子树分别并入iOS和Android代码库。

这样可以进行非常快的原型设计,并且如果有必要,可以将某些功能作为独立产品发布。

Udacity团队设计了很多原型,最终在React Native代码库中引入了第二个更大的功能。

时间线

  • 2016年8月:为功能1创建React Native代码库
  • 2016年11月:在Android上发布了功能1
  • 2016年11月:开始开发功能2
  • 2016年12月:设计功能3原型
  • 2017年1月:功能1开发结束
  • 2017年2月:功能2发布
  • 2017年3月:功能3原型设计结束
  • 2017年11月:在Android上更新功能2
  • 2017年12月:功能4原型作为独立app发布。最终由于性能问题取消了对原生的支持
  • 2018年2月:在iOS上更新功能2
  • 2018年4月:从Android中移除功能1
  • 2018年6月:从两个app中移除功能2

为什么要移除React Native?

原因很简单。因为剩下的唯一一个React Native功能已经没有用了,我们不再需要支持它。

或许这样问会更有趣:“为什么我们不继续在React Native上投入,以便获得新功能?”

此时,需要考虑几件事:

  1. 同时需要在两个平台上构建的功能数量越来越少
  2. Android特定需求在增加
  3. 对长期维护成本感到沮丧
  4. Android团队不愿继续使用React Native

用什么来取代React Native?

Udacity移除的React Native功能不再受支持,所以不需要进行任何替换。

初次尝试React Native给我们带来哪些好处?

  • 使用React Native进行跨平台构建非常容易
  • 可以引入React和Javascript生态系统中的库和工具
  • 能够同时在两个平台上对功能进行原型设计
  • 跨职能团队中的单个开发人员能够同时为两个平台构建大部分功能
  • 团队对React Native的整体理解有所改善

遇到了哪些问题?

在使用React Native期间,我们遇到了很多问,主要在于流程、使用场景,React Native本身的问题。

设计和体验方面的挑战

平台一致的UI/UX

因为要往现有的体验中加入一些新的页面,所以我们希望新的React Native代码能够遵循原生平台模式和现有的样式,这意味着我们不一定能为两个平台使用相同的UI设计。

要在React Native中确保每个平台本身的样式并不困难,但确实需要了解每个代码库的设计范式。至少需要对平台以及每个操作系统的自定义小部件进行检查。

对于我们来说,通常需要与每个平台的开发人员和设计人员沟通,以便了解需求是什么,否则如果两个平台使用同一个样式,有可能会导致在Android平台上的新功能体验与App的其他部分截然不同。

在更复杂的情况下,需要使用一些额外的平台特定代码来自定义App体验。

确保回退图标的行为是正确的就是一个例子。由于需要将新的React Native功能集成到现有的app中,要确保回退图标和后退按钮按的行为是正确的需要对Android原生代码和React Native代码做出特定的修改。

有一次,Android app的导航结构发生了变化,我们不得不修改React Native代码,只是为了要改变原生与React Native之间的集成方式。

React Native不需要有自己的Activity,我们把它们移到Fragment中,并放在带有BottomNavigationView的屏幕中,然后在它和其他原生Fragment之间协调状态。

这种类型的变更需要回到各自的代码库,做出更改,更新集成,并确保新的变更不会对iOS App产生负面影响。

设备特定问题

无论你要把这个叫作“碎片化”还是“多样化”,我们仍然要面对这样的事实:越来越多的Android设备有自己独一无二的配置。

很多时候,我们发现布局无法完美匹配不同尺寸的Android手机,在最新的iPhone或Pixel设备上运行流畅的动画在其他国家(在这些国家Android使用更为广泛)的低端设备上运行不佳。

这些肯定不只是React Native的问题,它们是Android平台开发常见的问题。但随着平台检查方面的工作量以及需要考虑到的因素数量的增加,我们不得不开始考虑,React Native到底可以为我们节省多少时间。

全球性增长

在我们使用React Native期间,国际化成为Android团队的一个焦点。我们的几个国际办事处要求对App进行本地化,并减少apk大小。

React Native的字符串本地化完全可以实现,尽管确实需要做出一些额外的设置。在我们的例子中,我们需要对代码库做出单独的修改。这增加了本地化的复杂性,因为向其他团队寻求本地化帮助并非理想的方式,同时也会降低React Native功能的本地化频率。

我们能够在一段时间内减少apk大小,但是React Native的大小可能会增加,对此我们无能为力。移除最后一个React Native功能后,我们的apk大小减少了约10MB。

集成挑战

与原生组件和导航结构的集成

根据我们的经验,对于独立的功能,将React Native集成到现有应用程序中可能非常简单,但如果要与现有组件紧密集成和交互,则可能会麻烦一些。

我们经常需要大量的桥接代码在原生和React Native组件之间进行通信。在React Native组件与现有导航结构集成的地方,每次发生变更时,都需要进行至少一次的桥接代码更新。

工具/构建问题

集成React Native需要对每个App的构建过程做出变更。我们使用CircleCI来构建项目,因此需要对它进行重新配置,以支持额外的React Native构建步骤。

而在Android方面,它并不像我们所预期的那么简单。

在进行重新配置后,CircleCI的构建时间增加了大约20%。

从代码库中移除最后一个React Native功能后,我们看到了以下改进:

  • CircleCI构建时间从15分钟减少到12分钟
  • 发布的apk大小从28.4MB降至18.1MB

Android团队也经常遇到Android/Gradle构建工具与React Native的冲突问题。

iOS团队也面临着相当大的挑战。

配置构建过程是一个很痛苦的事,因为我们的构建文件结构不是标准的。由于我们使用了单独的代码库,我们在srcroot/ReactNative目录中拉取React Native代码库,很多现有构建工具假设使用的是默认的app结构,即/ReactNative/ios/…ios。

此外,我们使用cocoapods进行依赖管理,最初建议用它来管理React Native依赖,但后来被弃用。我们的非标准文件结构也给我们带来了麻烦,我们不得不在Podfile中做一些令人讨厌的侵入性修改,让它从正确的位置读取配置文件。

由于cocoapods不再是标准的React Native依赖管理方式,因此Podfile的更新只能依赖于​​社区,而这些更新通常不太同步。比如css/Yoga已经推出更新,但Podfile引用的却是不正确的版本。最后,我们不得不做出一些侵入性的修改,使用sed和正则表达式来实现版本检查和安装。

iOS项目的CI也是一个痛点。我们现在必须添加一个npm依赖层,并确保在安装其他依赖之前它们是最新的。这给我们的构建步骤带来了额外的时间消耗。

还有一个问题会导致构建崩溃,比如一个版本的npm有package.lock文件,而另一个版本没有,这会导致我们在React Native升级过程中安装了不正确的依赖版本。

来自React Native的挑战

文档

React Native本身发展得很快,但文档却相对缺乏。特别是我们是第一次使用React Native,我们发现特定版本的文档多多少少有点不齐全。

那个时候,用于将React Native与现有项目集成的文档似乎很少,因此,在对CI做出构建配置时,这无疑增加了我们的难度。

随着React Native不断发展,文档和社区得到了改善。如果换到了今天,我们或许会更容易找到一些问题的答案。

导航

我们最初使用的是NavigationExperimental,它不是最容易使用的导航库。React Navigation出现后,迅速在社区中流行开来,在ReactNavigation的功能得到真正的完善之前,我们已经弃用了NavigationExperimental。

不过,如果不把一些东西强行组合在一起(例如在呈现模态流中推送流),有些事情在ReactNavigation中是做不到的。

性能

如前所述,我们也会注意到性能问题。

我们能够制作出一些非常漂亮的动画,这些动画在高配置的iOS和Android设备上看起来很棒,但在一些低配置Android设备上表现不佳。

进入app的React Native部分时,加载时间比我们预期的要长,而且它们看起来不像是无缝的过渡。

在对功能4进行原型设计时,图形渲染性能是一个非常大的问题,我们不得不使用原生体验取代了React Native。

滞后于原生平台

因为它不是与iOS或Android一起构建的,所以React Native有时会滞后于原生平台。在很大程度上,它依赖社区来提供新的原生功能。

其中一个例子就是迫切需要为iPhone X提供安全区域支持。我们最终选择在短时间内不使用SafeArea,因为React Native团队很快就会推出该特性。跨平台开发人员在使用SafeAreaView开发兼容性app时需要特别注意这个特定于平台的特性。

有时候,React Native也会在采用新平台需求方面表现滞后,例如Android app被要求在2018年8月之前采用API Level 26。而在这方面还有几个未解决的问题。

突破性的更新

React Native无法向后兼容,这点非常令人沮丧。比如,在React Native升级了它的底层React库后,PropType就被弃用了。

如果我们不维护自己的分支,很多第三方库就会变得无法使用。

来自维护方面的挑战

有时候,维护React Native代码库对我们来说是一个挑战。如前所述,Android通常需要额外的工作,无论是与现有代码集成还是修复UI问题。这导致iOS和Android使用了不同的React Native代码库分支,这样才能让两个平台之间不互相影响。

由于使用了不同的分支,开始出现代码分歧,并且用于解决冲突的工作流增加了。结果,对一个平台的更新并不会立即被添加到另一个平台中。

React Native的演化速度也给我们带来了挑战。由于可能会出现突破性变更,因此为了获得新功能或修复bug而进行的依赖更新的速度变慢了。

另外,有时这会导致摩擦力增加,从而降低了代码的维护速度。由于团队规模小,带宽有限,如果不是简单的、可以快速修复的问题,那么问题就不太可能很快得到解决,因为这可能需要额外的开发工作量。

因为引入了React Native,所以我们并不总能清楚地知道bug处于什么级别。它是否存在于两个平台中?还是只在一个平台上出现?如果只在一个平台上出现,是在原生代码中还是在React Native代码中?这些问题所带来的复杂性有时候会减慢QA过程。

当需要修复React Native代码库中的问题时,我们现在必须同时考虑iOS和Android,并且可能需要使用3个开发栈,而不是1个。

此外,不是所有人都觉得React Native效率很高,能够迅速上手并修复问题的开发人员的数量也减少了。

我们本该可以做哪些不一样的事情?

我相信,我们遇到的一些问题是我们的场景所固有的,但是我们其实可以做些不一样的事情来缓解其中的一些问题。

减少代码分歧

我们本可以更好地让React Native代码库中的变更与每个平台的App保持同步。我相信保持这些更新同步将有助于增强我们的跨平台开发意识。

多在设备上进行测试,特别是在Android上,可能可以在早期就发现更多的UI和性能问题,并在发布之前修复它们。在开发新功能之前解决旧问题,可以减少代码分歧的数量。

更一致的设计

如果从一开始就设计更具体方案可能有助于改善功能的原生外观。比如使用与原生App一致的文本和边值,而不是使用新值,并在两个平台上使用它们。

团队成员需要学习

对React Native不太熟悉的团队成员可以更努力地尝试融入新的开发栈,这样就会有更多的人能够快速解决问题。

有没有适合使用React Native的场景?

我相信,我们团队中没有人会认为React Native一无是处。我当然相信React Native有它适合的场景。

  • 你是否需要在两个平台上从头开始快速地构建新app?
  • 你是否正在开发一个App或功能,要求在不同的平台上具有相同的外观和行为?
  • 你的Javascript开发人员是不是有多余的时间可用于开发移动应用?

如果对这些问题中的任何一个答案“是”,那么React Native很可能适合你。

特别是如果你有Javascript和React经验,并且正在寻找一个不需要太多原生代码就能构建的应用程序,那么React Native是一个非常有吸引力的选择。这样你就能够马上开始开发,而无需去学习2种不同的技术栈。

对于新开发的完全跨平台的app,React Native也是一个很好的选择。

我们会再次使用React Native吗?

iOS和Android团队有不同的意见。

iOS 有可能。iOS团队通常很乐意使用React Native,并考虑使用它开发新功能。此外,在产品方面,我们的PM对在iOS上运行React Native比对Android更有信心。

Android 不会。理想情况下,Android团队将来不会继续在React Native上投入。我们发现与React Native组件集成的过程很麻烦,并且在Android设备上的体验达不到预期。

此外,还有一种倾向,坚持使用单一的开发栈,而不是在Android框架之上添加新的抽象层,或引入可能的错误。

我们的印象是,使用React Native开发Android新功能的速度更快,但长期来看,新功能从早期阶段到能够完美发布需要更长的时间。

我们会再次使用另一种跨平台解决方案吗?

从团队方面来看,在不久的将来,我们可能不会在跨平台开发上继续投入。iOS团队可以使用React Native构建一些特定于iOS的东西,因为他们通常更喜欢这种体验。

就个人而言,团队的成员将继续关注React Native和Flutter。随着React Native和Flutter等解决方案的不断发展,我们也将继续对它们做出评估。

我们对React Native是否适合我们的团队和路线图有了更好的理解。在进行技术选型时,我们可以利用这些信息做出更加明智的决策。

我们看到了React Native的优点以及局限性。我们能否明确地给出React Native是否适合你的定论?

不能。

但是,在评估React Native是否可用在你们的项目中时,希望我们的经验可以作为参考。

查看英文原文:https://engineering.udacity.com/react-native-a-retrospective-from-the-mobile-engineering-team-at-udacity-89975d6a8102