- 原文地址:React Native at Airbnb: The Technology
- 原文做者:Gabriel Peal
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:ALVINYEH
- 校对者:ssshooter、LeeSniper
这是系列博客文章中的第二篇,本文将会概述咱们使用 React Native 的经验,以及 Airbnb 移动端接下来要作的事情。javascript
React Native 自己在 Android,iOS,Web 和跨平台框架的各个部分上,是一个相对较新且快速迭代的平台。两年后,咱们能够颇有把握地说,React Native 在不少方面都是革命性的。这是移动设备的转变范例,咱们可以从众多目标中得到收益。然而,它的好处并不是没有明显的痛点。css
React Native 的主要好处在于,你所编写的代码可以同时在 Andriod 和 iOS 上运行。使用 React Native 的大多数功能均可以实现 95 - 100% 的共享代码,和 0.2% 不一样平台须要用的的文件(.android.js/.ios.js)。html
咱们开发了一种名为 DLS 的跨平台设计语言。同时拥有每一个组件的 Android、iOS、React Native 和 Web 版本。拥有统一的设计语言能够实现编写跨平台代码的功能,这意味着设计,组件名称和屏幕能够跨平台保持一致。可是,咱们仍然可以在适用的状况下,作出适合平台的决策。例如,咱们在 Andriod 上使用原生的 Toolbar,iOS 上使用 UINavigationBar,但咱们须要在 Andriod 上隐藏 disclosure indicators,由于这不符合 Andriod 平台设计准则。前端
咱们选择了重写组件,而不是封装原生组件。由于每一个平台分别制做适用的 API 会更加可靠,而且能够减小 Android 和 iOS 工程师的维护开销,他们可能不清楚应该如何正确测试 React Native 中更改的代码。可是,它确实会致使同一组件的原生和 React Native 版本不一样步的平台之间出现碎片。java
React 成为最受开发者欢迎的 Web 框架也是有缘由的。那就是它很是简单但功能强大,适用于大型代码库。咱们很是喜欢的特色是:node
在使用 React Native 进行开发时,咱们可以稳定地使用热加载来测试咱们在 Android 和 iOS 上的改动,过程只需一两秒钟的时间。即便原生应用不遗余力也无法达到 React Native 实现的迭代速度。在最理想的状况下,原生编译时间为 15 秒,但对于完整项目的构建,竟高达 20 分钟。react
咱们开发了普遍的集成到咱们的原生基础框架,诸如网络、国际化、实验、共享元素转换、设备信息,账户信息等许多核心组件都封装在一个 React Native API 中。这些桥接是一些更复杂的部分,由于咱们想要将现有的 Android 和 iOS API 封装成对 React 一致且规范的东西。尽管经过快速迭代和新基础架构的开发,来保持这些桥接是最新的,但基础架构团队的投入能简化产品工做。android
若是没有大量投入基础架构,React Native 会致使糟糕的开发人员和用户体验。所以,咱们不相信 React Native 能够在没有重大和持续投入的状况下,直接应用到现有的应用程序中。ios
性能是React Native 最大的问题之一。可是,实践中遇到这个问题的机会不大。咱们的大多数使用了 React Native 的屏幕都像原生的同样流畅。咱们每每会总在一个单一的维度中去考虑性能。咱们常常看到移动端工程师认为 JS,“比Java慢”。然而,在不少状况下,移动端主线程的业务逻辑和布局均可以提升渲染性能。css3
当咱们确实发现性能问题时,大多数是由过分渲染引发的,这能够经过有效地使用 shouldComponentUpdate,removeClippedSubviews,和使用 Redux 来解决。
然而,初始化和初识渲染时间(下面概述)使得 React Native 在启动屏幕,Deep Links 表现不佳,而且在屏幕之间导航时增长了 TTI 时间。另外,由于 Yoga 在 React Native 组件和原生视图之间进行了转换,因此丢失帧的屏幕很难调试。
咱们使用了 Redux 进行状态管理,发现这种方法很是有效。不但防止了 UI 与状态不一样步的问题,在屏幕之间也能轻松实现数据共享。可是,Redux 以其模板和相对困难的学习曲线而声名狼藉。咱们为一些常见模板提供了生成器,但它仍然是在使用 React Native 时,最具挑战性的部分和混淆之一。值得注意的是,这些挑战不是 React Native 特有的。
因为 React Native 中的全部内容均可以经过原生代码进行桥接,所以咱们最终可以在开始时构建许多咱们不肯定的事情,例如:
咱们在网络方面上使用 eslint 的历史很是悠久,此次咱们也能够利用它。不过,Airbnb 是开创 Prettier 的第一个平台。咱们发现它能够有效减小 PR 上的麻烦。如今,咱们的网络基础架构团队正在积极研究 Prettier。
咱们还用分析来衡量渲染时间和性能,以肯定哪些屏幕是性能问题调查的首要任务。
因为 React Native 比咱们的网络基础结构更小、更新,所以是良好的测试新想法的平台。咱们为 React Native 建立的许多工具和想法如今都被 Web 采用。
多亏了 React Native 动画 库,咱们可以实现流畅动画,甚至交互驱动的动画,如视差滚动。
因为 React Native 会运行 React 和 JavaScript,所以咱们可以利用大量的 Javascript 项目,例如 Redux、Reselect、Jest 等。
React Native 使用了 Yoga 来处理布局。这是个跨平台的 C 语言库,经过 flexbox API 处理布局计算。早些时候,咱们受到 Yoga 的局限,例如缺少长宽比,不过在后续更新增添了。此外,像 flexbox froggy 这样有趣的教程,能让你在上手的时候更加享受。
在 React Native 探索的后期,咱们开始一次性为 Web,iOS 和 Android 构建项目。鉴于 Web 也使用 Redux,咱们发现大量代码能够在 Web 和原平生台上共享,无需作任何更改。
React Native 相对 Android 或 iOS 来讲,略显不够成熟。它很新,也有野心,而且迭代速度很是快。虽然 React Native 在大多数状况下都能很好地工做,但有些状况下,它的不成熟可能会显现出来,使用原生就能垂手可得实现的事情变得很是困难。不幸的是,这些状况很难预测,并且可能须要几小时到几天的时间才能解决。
因为 React Native 还不成熟,咱们有时须要修补 React Native 源码。除了将问题反馈给 React Native 以外,咱们还必须维护一个分支,咱们能够在其中快速合并更改并升级版本。在过去两年中,咱们不得不在 React Native 添加大约 50 次 commit。这使得升级 React Native 的过程很是痛苦。
JavaScript 是一种无类型语言。缺少类型安全既难以扩展,也成为习惯于类型化语言的移动端工程师争论的焦点,不然他们可能会对学习 React Native 感兴趣。咱们探讨了采用 flow 的方式,但隐晦的错误消息致使了使人沮丧的开发者体验。咱们还探讨了 TypeScript,但将其整合到咱们现有的基础架构中时,如 babel和 metro bundler 是有问题的。不过,咱们正在继续积极研究 Web 上的 TypeScript。
JavaScript 一个无类型的反作用是,重构很是困难且容易出错。重命名一些属性,特别是带有通用名称的属性(如 onClick )或经过多个组件传递的属性,对于准确地重构来讲是一场噩梦。更糟糕的是,重构在生产环境中崩溃,而不是在编译时,很难对其进行适当的静态分析。
React Native 的一个微妙和棘手的方面是,它须要在 JavaScriptCore 环境上执行。如下是咱们遇到的问题:
学习平台既困难又费时。大多数人只能很好地了解一或两个平台。React Native 库有原生桥接,例如地图,视频等,开发者须要对三个平台都有相同的认识才能实现。咱们发现大多数 React Native 开源项目都是由有一两次经验的人所编写的。这致使了 Android 或 iOS 上的不一致或意想不到的错误。
在 Android 上,许多 React Native 库也要求你使用 node_modules 的相对路径,而不是发布与社区所指望的不一致的 Maven Artifact。
咱们在 Android 和 iOS 上积累了多年的原生基础架构。可是,在 React Native 中,咱们从彻底空白的状态开始,不得不编写或建立全部现有基础架构的桥接。这意味着,有时产品工程师须要一些尚不存在的功能。这种状况,他们要么在一个不熟悉的平台上工做,要么在项目范围以外构建,或者干等到有人建立这个功能。
咱们使用 Bugsnag 进行 Android 和 iOS 崩溃报告。虽然 Bugsnag 一般在两个平台上能正常工做,但它不太靠谱,而且须要比在其余平台上作更多的工做。因为 React Native 在行业中相对较新且罕见,所以咱们必须构建大量基础架构,例如内部上传的源地图,而且必须与 Bugsnag 合做才能执行诸如发生在 React Native 过滤器崩溃等事件。
因为 React Native 周围的自定义基础架构数量众多,偶尔也会出现严重问题,例如未报告崩溃或源地图未正确上传。
最后,若是问题跨越 React Native 和原生代码,调试 React Native 崩溃每每更具挑战性,由于堆栈跟踪不能在 React Native 和原生代码之间跳转。
React Native 有 桥接 API 用于原生和 React Native 之间进行通讯。虽然它能按预期正常工做,但编写起来很是麻烦。首先,它要求全部三种开发环境都要正确设置。咱们也遇到了不少来自 JavaScript 的类型不正确的问题。例如,整数一般是由字符串封装的,这个问题直到桥接后才能被察觉。更糟糕的是,有时 iOS 会悄无声息地失败,而 Android 则会崩溃。到 2017 年末,咱们开始研究从 TypeScript 定义自动生成的桥接代码,但为时已晚。
在 React Native 首次渲染以前,你必须初始化其运行时。不幸的是,即便在高端设备上,咱们的应用也须要几秒钟的时间。因此,几乎是不可能使用 React Native 来启动屏幕。咱们经过在应用程序启动时初始化 React Native 来缩短第一次渲染时间。
与原生屏幕不一样,渲染 React Native 须要至少一个完整的主线程 -> JS -> Yoga 布局线程 -> 主线程返回以前,而后才有足够的信息来第一次渲染屏幕。咱们能够看到 iOS 平均初始 p90 渲染 280ms,而 Android 须要 440ms。在 Android 上,咱们使用一般用于共享元素转换的 postponeEnterTransition API 来延迟显示屏幕直到它完成渲染。在 iOS 上,咱们遇到了问题,从 React Native 快速设置导航栏配置。所以,咱们为全部 React Native 屏幕过渡添加了 50ms 的仿真延迟,以防止配置加载后导航栏闪烁。
React Native 对应用程序大小也有不可忽视的影响。在 Android 上,React Native(Java + JS + 原生库,例如 Yoga + Javascript 运行时)的总大小为每一个 ABI 8MB。在一个 APK 中使用 x86 和 arm(仅 32 位),体积将接近 12MB。
因为这个问题,咱们仍然不能在 Android 上安装一个 64 位的 APK。
咱们避免在涉及复杂手势的页面上使用 React Native,由于 Android 和 iOS 的触摸子系统很是不一样,以致于整理出一套统一的 API 对整个 React Native 社区来讲都具备挑战性。然而,这项工做仍在继续进行,react-native-gesture-handler 最近已经发布 1.0 版本。
React Native 在这方面取得了一些进展,好比 FlatList 库。可是,它们远不及 Android 上的 RecyclerView 或是 iOS 上的 UICollectionView 的成熟度和灵活性。因为多进程的问题,许多限制难以克服。Adapter 数据没法同步访问,这会致使视图闪烁,由于它们在快速滚动时进行了异步渲染。另外,文本也没法同步测量,所以 iOS 没法使用预先计算的 cell 高度进行某些优化。
尽管大多数 React Native 升级都很微不足道,但有一些却使人很是痛苦。尤为是, React Native 0.43(2017 年 4 月)至 0.49(2017 年 10月)版本几乎没法使用,由于其中使用了 React 16 alpha 和 beta。这是个大问题,由于大多数专为 Web 使用而设计的 React 库不支持之前发布的 React 版本。争论这次升级的适当依赖关系的过程,对 2017 年中其余 React Native 基础架构工做形成重大损害。
在 2017 年,咱们进行了一次辅助功能大修,其中咱们投入了大量的精力,确保残疾人士可使用 Airbnb 预订来知足他们的房源须要。可是,React Native 关于辅助功能的 API 有不少漏洞。为了知足甚至最小可接受的辅助功能条,咱们必需要维护 React Native 的一个分支,来合并修复程序。对于这些状况,Android 或 iOS 上的一行修复须要数天时间,才能肯定如何将其添加到 React Native,而后 cherry-pick,接着在 React Native Core 上提交问题,并在接下来的几周内对其进行跟踪。
咱们不得不面对一些难以解决的、很是奇怪的崩溃。例如,咱们目前在 @ReactProp 注解中遇到了这个崩溃,而且没法在任何设备上复现,即便是和那些持续崩溃的设备具备相同硬件和软件也是如此。
Android 常常会去清理后台进程,但给了它们一个同步把状态保存在 bundle 中的机会。可是,在 React Native 上,全部状态只能在 JS 线程中访问,所以没法同步进行。即便状况并不是如此,做为状态存储的 Redux 与此方法也不兼容,由于它混合了包含可序列化和不可序列化数据,而且可能包含比 saveInstanceState 包中容纳的更多类型的数据,这会致使在生产环境下崩溃。
这是系列博客文章的第二部分,重点讲述了咱们使用 React Native 的经验,以及 Airbnb 移动端接下来要作的事情。
若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。