在本文章中,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 为什么以及如何采用 RN ?
为什么要引入它?
当时,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