6ab1b5c90d6c5057762a0ecfaa03fe62
[译] Airbnb 使用 React Native 的技术细节

Medium 原文 React Native at Airbnb: The Technology

Airbnb 发布了一系列文章,描述了他们使用 React Native 的体验以及他们在移动端的下一步规划,本文是该系列文章中的第二篇。

React Native 是 Android、iOS 和 web 的跨平台框架,也是一个相对较新、快速迭代的平台。React Native 出现两三年后,我终于可以负责任地说,它在很多方面都具有革命性,这种转变让我们获益良多。然而,它带来好处的同时也有很多地方让人痛苦。

好的地方

跨平台

React Native 最大的优势就是一份代码可以在 Android 和 iOS 上同时原生运行。使用 React Native 实现的大部分功能都能共享 95-100% 的代码,只有 0.2% 的文件是平台指定的(.android.js/.ios.js)。

DLS(统一设计语言系统)

我们开发了一种叫做 DLS 的跨平台设计语言,每个组件都有 Android、iOS、React Native 和 web 版本。拥有统一的设计语言有利于实现跨平台的产品功能,因为设计、组件名称以及界面在不同平台间能够保持一致,同时在必要的时候也可以适应特定平台。例如,我们在 Android 上使用原生 Toolbar,在 iOS 上使用 UINavigationBar,此外还要在 Android 平台上隐藏 disclosure indicator——因为不符合 Android 平台设计指南。

我们选择重写组件,而不是包装原生组件,因为为每个平台编写特定 API 会更加可靠,同时还能减少维护成本,毕竟有些 Android 和 iOS 程序员并不知道如何测试 React Native 中的更改。然而,这样会导致平台间的碎片化——同一组件的原生和 React Native 版本会不同步。

React

React 之所以成为最受欢迎的 web 框架只有一个原因——简单而又强大,适用于大型代码库。我们尤其钟爱下面几点:

  • 组件:React 组件通过明确定义的属性和状态强制分离关注点(separation of concerns),大大提升了 React 的可扩展性。
  • 简化的生命周期:众所周知,Android 以及 iOS(相对来说好一点)的生命周期很复杂。功能性、响应式的 React 组件从根本上解决了这个问题,让 React Native 学起来比 Android 和 iOS 简单很多。
  • 声明式编码(Declarative):React 的声明式编码特性可以让 UI 与基本状态保持同步。

迭代速度

使用 React Native 进行开发时,我们依然能够在 Android 和 iOS 上使用热加载(hot reloading)功能快速(只要一两秒)测试变更,非常可靠。尽管我们的原生应用都以构建性能作为首要任务,但还是难以接近 React Native 的迭代速度。在最好的情况下,原生编译耗时 15 秒,但完整构建可能还会花上 20 分钟。

基础组件投入

我们为原生基础组件开发了大量集成,所有核心组件例如网络、国际化(i18n)、实验、共享元素转场动画(shared element transitions)、设备信息、账户信息等等都被打包为单独的 React Native API。我们想要把现有的 Android 和 iOS API 打包为对 React 来说一致且规范的东西,通过快速迭代和开发新的基础组件来保持 bridge 处于最新状态,尽管这是一个没有终点的过程,但可以让产品工作轻松许多。

如果没有对基础组件的大量投入,React Native 会导致糟糕的开发与用户体验,所以我们只有持续开发基础组件,这样才能直接在现有 app 上加入 React Native 支持。

性能

很多人质疑 React Native 的性能,但在实际应用中这并不是一个问题。大部分 React Native 界面都和原生一样流畅。我们往往用单一维度来考量性能,例如移动端工程师经常认为 JS “比 Java 慢”,然而在大部分情况下,从主线程移除业务逻辑和布局可以实际地提升渲染性能。

如果的确出现了性能问题,通常都是由于过量渲染,使用 shouldComponentUpdateremoveClippedSubviews 可以有效缓解,同时要优化对于 Redux 的使用。

然而,初始化和首次渲染时间(下面会说)使得 React Native 在启动屏和深层链接(deeplink)方面性能不佳,在界面间导航时还会增加 TTI 时间。此外,丢帧的界面很难调试,因为 Yoga 会在 React Native 和原生视图间进行转换。

Redux

我们使用 Redux 来管理状态,效果很好,可以防止 UI 与状态不同步,在不同界面间共享数据也更简单了。然而大家都知道,Redux 有大量样板代码,学习曲线也相对陡峭。我们为一些常见的模板提供了生成器,但 Redux 仍然是使用 React Native 时最大的挑战与引起混乱的根源之一。值得注意的是,并不是只有 React Native 存在这种情况。

原生的支持

因为 React Native 里的所有东西都可以与原生代码进行桥接,我们最终构建了一些一开始认为难以实现的东西,例如:

  1. 共享元素转场动画:借助 Android 和 iOS 上原生共享元素代码的支持,我们构建了 <SharedElement> 组件,甚至可以用在原生和 React Native 界面之间。
  2. Lottie:通过打包 Android 和 iOS 上现有的库让我们可以在 React Native 里使用 Lottie。
  3. 原生网络栈:React Native 在两个平台上使用了我们现有的原生网络栈和缓存。
  4. 其它核心基础组件:和网络一样,我们打包了其它的现有基础组件,例如国际化、实验等等,与 React Native 无缝结合。

静态分析

我们在 web 上使用了很长一段时间 eslint,但是在 Airbnb 首创了 prettier,可以有效减少 PR 上的无效和重复讨论。现在我们的 web 基础组件团队正在积极研究 prettier。

我们还使用 analytics 来衡量渲染时间与性能,搞清楚应该先研究哪些界面的性能问题。

由于 React Native 相比我们的网络基础组件来说更加轻量、更加新颖,所以很适合用于测试新想法。我们为 React Native 创建的许多工具和创意现在都被 web 端采用了。

动画

感谢 React Native 的 Animated 库,让我们可以实现流畅的动画甚至是交互驱动的动画,例如滚动视差。

JS/React 的开源资源

由于 React Native 完全运行在 React 和 javascript 上,我们可以利用极为广泛的 javascript 项目,例如 redux、reselect、jest 等等。

Flexbox

React Native 使用 Yoga 来处理布局,Yoga 是一个跨平台 C 库,通过 flexbox API 来计算布局。一开始我们遇到了一些 Yoga 的限制,例如没有长宽比(aspect ratio),但在后续的更新中 Yoga 陆续添加了这些缺失的东西。此外,flexbox froggy 等好玩的教程让 flexbox上手变得轻松愉快。

和 web 合作

在探索 React Native 的后期,我们开始一次同时为 web、iOS 和 Android 进行构建。由于 web 也使用 Redux,所以大量代码都可以无需修改直接在 web 和原生平台间共享。

不好的地方

React Native 的不成熟

React Native 不如 Android 和 iOS 成熟,它更新颖、更有野心、迭代速度非常快。虽然 React Native 在大部分情况下都适用,但在某些情况下它的不成熟让一些本来在原生中易如反掌的事情变得寸步难行。不幸的是,这些情况很难预测,可能要花上几个小时到几天才能解决。

维护 Fork 的 React Native

由于 React Native 的不成熟,有时我们需要修补 React Native 的源码。除了直接 contribute 给 React Native,我们还要维护一份 fork 的 repo,以便快速 merge 变更并更新版本。两年来,我们不得已给 React Native 提交了大约 50 次 commit,这让 React Native 的升级过程变得异常痛苦。

top Created with Sketch.