React Native at Airbnb: The Technology
This is the second in a series of blog posts in which we outline our experience with React Native and what is next for mobile at Airbnb.
React Native itself is a relatively new and fast-moving platform in the cross-section of Android, iOS, web, and cross-platform frameworks. After two years, we can safely say that React Native is revolutionary in many ways. It is a paradigm shift for mobile and we were able to reap the benefits of many of its goals. However, its benefits didn’t come without significant pain points.
React Native本身是Android，iOS，Web和跨平台框架的一个相对较新且快速移动的平台。 两年后，我们可以放心地说，React Native在很多方面都是革命性的。 这是移动设备的典范转变，我们能够从众多目标中获得收益。 然而，它的好处并非没有重大的痛点。
What Worked Well
The primary benefit of React Native is the fact that code you write runs natively on Android and iOS. Most features that used React Native were able to achieve 95–100% shared code and 0.2% of files were platform-specific (.android.js/.ios.js).
React Native的主要优点是您编写的代码可以在Android和iOS的设备上原生的运行。 大多数使用React Native的功能都能够实现95-100％的共享代码，0.2％的文件是特定于平台的（* .android.js / *。ios.js）。
Unified Design Language System (DLS)
We developed a cross-platform design language called DLS. We have Android, iOS, React Native, and web versions of every component. Having a unified design language was amenable to writing cross-platform features because it meant that designs, component names, and screens were consistent across platforms. However, we were still able to make platform-appropriate decisions where applicable. For example, we use the native Toolbar on Android and UINavigationBar on iOS and we chose to hide disclosure indicators on Android because they don’t adhere to the Android platform design guidelines.
我们开发了一种名为DLS的跨平台设计语言。 我们拥有Android，iOS，React Native和每个组件的Web版本。 拥有统一的设计语言可以编写跨平台功能，因为这意味着设计，组件名称和屏幕跨平台保持一致。 但是，我们仍然能够在适用的情况下做出适合平台的决策。 例如，我们在Android上使用原生工具栏，在iOS上使用UINavigationBar，我们选择在Android上隐藏显示出来的指示器（discosure indicators），因为它们不符合Android平台设计指南。
We opted to rewrite components instead of wrapping native ones because it was more reliable to make platform-appropriate APIs individually for each platform and reduced the maintenance overhead for Android and iOS engineers who may not know how to properly test changes in React Native. However, it did cause fragmentation between the platforms in which native and React Native versions of the same component would get out of sync.
我们选择重写组件，而不是包装本地组件，因为为每个平台分别制作适合平台的API更加可靠，并减少了可能不知道如何正确测试React Native中的更改的Android和iOS工程师的维护开销。 但是，它确实会导致同一组件的naitve和React Native版本不同步的平台之间出现碎片。（我的理解是RN的支持比如是iOS 10，但此时iOS11出来后废弃了一些东西，又添加了一些东西，那这些未适配的东西，就会使得代码变的碎片化）。
There is a reason that React is the most-loved web framework. It is simple yet powerful and scales well to large codebases. Some of the things we particularly like are:
- Components:React Components enforce separation of concerns with well-defined props and state. This is a major contributor to React’s scalability.
- Simplified Lifecycles: Android and, to a slightly lesser extent, iOS lifecycles are notoriously complex. Functional reactive React components fundamentally solve this problem and made learning React Native dramatically simpler than learning Android or iOS.
- Declarative: The declarative nature of React helped keep our UI in sync with the underlying state.
React是最受欢迎的Web框架是有原因的。 它非常简单但功能强大，适用于大型代码库。 我们特别喜欢的一些东西是：
组件： React组件通过明确定义的props和state强来强制分离关注点（解耦？？）。 这是React可扩展性的主要方面。
简化的生命周期: Android和iOS，并且在一定程度上，iOS生命周期非常复杂。 函数反应式的React组件从根本上解决了这个问题，使学习React Native比学习Android或iOS更简单。
While developing in React Native, we were able to reliably use hot reloadingto test our changes on Android and iOS in just a second or two. Even though build performance is a top priority for our native apps, it has never come close to the iteration speed we achieved with React Native. At best, native compilation times are 15 seconds but can be as high as 20 minutes for full builds.
在使用React Native进行开发时，我们能够可靠地使用热重新加载，在短短一两秒内测试Android和iOS上的更改。尽管构建性能是我们本机应用的首要任务，但它从来没有接近我们通过React Native实现的迭代速度。充其量，本地编译时间为15秒，但对于完整版本可高达20分钟。
Investing in Infrastructure
We developed extensive integrations into our native infrastructure. All core pieces such as networking, i18n, experimentation, shared element transitions, device info, account info, and many others were wrapped in a single React Native API. These bridges were some of the more complex pieces because we wanted to wrap the existing Android and iOS APIs into something that was consistent and canonical for React. While keeping these bridges up to date with the rapid iteration and development of new infrastructure was a constant game of catch up, the investment by the infrastructure team made product work much easier.
Without this heavy investment in infrastructure, React Native would have led to a subpar developer and user experiences. As a result, we don’t believe React Native can be simply tacked on to an existing app without a significant and continuous investment.
我们开发了广泛的集成到我们的原生基础设诸如网络，i18n，实验，共享元素转换，设备信息，帐户信息等许多核心组件都包装在一个React Native API中。这些桥梁是一些更复杂的部分，因为我们想要将现有的Android和iOS API包装成对React一致且规范的东西。尽管通过快速迭代和新基础架构的开发来保持这些桥梁是最新的，但基础架构团队的投资使产品工作变得更加容易。
如果没有对基础架构进行大量投资，React Native会导致降低开发人员的开发体验和用户体验。 因此，我们不相信React Native可以在没有重大和持续投资的情况下直接应用到现有的应用程序中。
One of the largest concerns around React Native was its performance. However, in practice, this was rarely a problem. Most of our React Native screens feel as fluid as our native ones. Performance is often thought of in a single dimension. We frequently saw mobile engineers look at JS and think “slower than Java”. However, moving business logic and layout off of the main thread actually improves render performance in many cases.
When we did see performance issues, they were usually caused by excessive rendering and were mitigated by effectively using shouldComponentUpdate, removeClippedSubviews, and better use of Redux.
However, the initialization and first-render time (outlined below) made React Native perform poorly for launch screens, deeplinks, and increased the TTI time while navigating between screens. In addition, screens that dropped frames were difficult to debug because Yoga translates between React Native components and native views.
React Native最大的问题之一就是它的表现。 但是，在实践中，这很少是一个问题。 我们的大多数React Native屏幕都像我们的本地屏幕一样流畅。 绩效往往被认为是单一维度。 我们经常看到移动工程师看JS，认为“比Java慢”。 但是，在很多情况下，移动主线程的业务逻辑和布局实际上可以提高渲染性能。
然而，初始化和第一渲染时间（（下面概述））使得React Native在启动屏幕，深度链接时表现不佳，并且在屏幕之间导航时增加了TTI时间。 另外，因为Yoga(一个布局框架)在React Native组件和本地视图之间转换，因此丢失帧的屏幕很难调试。
We used Redux for state management which we found effective and prevented the UI from ever getting out of sync with state and enabled easy data sharing across screens. However, Redux is notorious for its boilerplate and has a relatively difficult learning curve. We provided generators for some common templates but it was still one of the most challenging pieces and source of confusion while working with React Native. It is worth noting that these challenges were not React Native specific.
我们使用Redux进行状态管理，我们发现这种状态管理非常有效，并且防止了UI与状态不同步，并且可以轻松跨屏幕共享数据。 但是，Redux以其样板而闻名，并且学习曲线相对较为困难。 我们为一些常见模板提供了生成器，但它仍然是使用React Native时最具挑战性的部分和混淆之一。 值得注意的是，这些挑战不是React Native特有的。
Backed by Native
Because everything in React Native can be bridged by native code, we were ultimately able to build many things we weren’t sure were possible at the beginning such as:
- Shared element transitions: We built a <SharedElement> component that is backed by native shared element code on Android and iOS. This even works between native and React Native screens.
- Lottie: We were able to get Lottie working in React Native by wrapping the existing libraries on Android and iOS.
- Native networking stack: React Native uses our existing native networking stack and cache on both platforms.
- Other core infra: Just like networking, we wrapped the rest of our existing native infrastructure such as i18n, experimentation, etc. so that it worked seamlessly in React Native.
共享元素转换：我们构建了一个由Android和iOS上的本机共享元素代码支持的<SharedElement>组件。 这甚至适用于本机和React Native屏幕。
We have a strong history of using eslint on web which we were able to leverage. However, we were the first platform at Airbnb to pioneer prettier. We found it to be effective at reducing nits and bikeshedding on PRs. Prettier is now being actively investigated by our web infrastructure team.
We also used analytics to measure render times and performance to figure out which screens were the top priority to investigate for performance issues.
Because React Native was smaller and newer than our web infrastructure, it proved to be a good testbed for new ideas. Many of the tools and ideas we created for React Native are being adopted by web now.
我们在网络上使用eslint的历史非常悠久，我们可以利用它。 不过，我们是Airbnb开创更漂亮的第一个平台。 我们发现它可以有效减少PRs上的麻烦和麻烦(这里翻译的有点理解不到位)。 现在，我们的网络基础架构团队正在积极调查漂亮的东西。
由于React Native比我们的网络基础设施更小更新，因此它被证明是新想法的良好测试平台。 我们为React Native创建的许多工具和想法现在都被web采用。
Thanks to the React Native Animated library, we were able to achieve jank-free animations and even interaction-driven animations such as scrolling parallax.
得益于React Native Animated库，我们能够实现无干扰的动画，甚至是交互驱动的动画，例如滚动视差。
JS/React Open Source
JS / React开源
React Native handles layout with Yoga, a cross-platform C library that handles layout calculations via the flexbox API. Early on, we were hit with Yoga limitations such as the lack of aspect ratios but they have been added in subsequent updates. Plus, fun tutorials such as flexbox froggy made onboarding more enjoyable.
React Native使用Yoga处理布局，瑜伽是一个跨平台的C库，可通过Flexbox API处理布局计算。 早些时候，我们遇到了Yoga限制，例如缺乏长宽比，但在后续更新中添加了它们。 此外，有趣的教程，如flexbox froggy使上手更愉快。
Collaboration with Web
Late in the React Native exploration, we began building for web, iOS, and Android at once. Given that web also uses Redux, we found large swaths of code that could be shared across web and native platforms with no alterations.
在React Native探索的后期，我们开始一次为web，iOS和Android构建。 鉴于Web还使用Redux，我们发现大量代码可以在Web和本机平台上共享，而无需任何更改。
What didn't work well
React Native Immaturity
React Native is less mature than Android or iOS. It is newer, highly ambitious, and moving extremely quickly. While React Native works well in most situations, there are instances in which its immaturity shows through and makes something that would be trivial in native very difficult. Unfortunately, these instances are hard to predict and can take anywhere from hours to many days to work around.
React Native比Android或iOS更不成熟（或者说是稳定？）。 它更新，更有野心，并且更新速度非常快。 虽然React Native在大多数情况下都能很好地工作，但有些情况下，它的不稳定可能会显示出来，并且会使原生开发中的一些微不足道的事情变得非常困难。 不幸的是，这些情况很难预测，可能需要几小时到几天的时间才能解决。（深有体会，译者注）
Maintaining a Fork of React Native
Due to React Native’s immaturity, there were times in which we needed to patch the React Native source. In addition to contributing back to React Native, we had to maintain a fork in which we could quickly merge changes and bump our version. Over the two years, we had to add roughly 50 commits on top of React Native. This makes the process of upgrading React Native extremely painful.
由于React Native的不成熟，我们有时需要修正React Native源码。 除了向React Native做出贡献之外，我们还必须维护一个分支，以便我们能够快速合并更改并使版本崩溃。 在过去两年中，我们不得不在React Native之上添加大约50次提交。 这使得升级React Native的过程非常痛苦。
React Native Open Source Libraries
Learning a platform is difficult and time-consuming. Most people only know one or two platforms well. React Native libraries that have native bridges such as maps, video, etc. requires equal knowledge of all three platforms to be successful. We found that most React Native Open source projects were written by people who had experience with only one or two. This led to inconsistencies or unexpected bugs on Android or iOS.
On Android, many React Native libraries also require you to use a relative path to node_modules rather than publishing maven artifacts which are inconsistent with what is expected by the community.
学习平台既困难又耗时。 大多数人只能很好地了解一个或两个平台。 React具有原生通信的bridge（如地图，视频等）的本机库需要对所有三个平台都有相同的认识才能取得成功。 我们发现大多数React Native开源项目都是由有一两次经验的人撰写的。 这导致了Android或iOS上的不一致或意外的错误。
Parallel Infrastructure and Feature Work
We have accumulated many years of native infrastructure on Android and iOS. However, in React Native, we started with a blank slate and had to write or create bridges of all existing infrastructure. This meant that there were times in which a product engineer needed some functionality that didn’t yet exist. At that point, they either had to work in a platform they were unfamiliar with and outside the scope of their project to build it or be blocked until it could be created.
我们在Android和iOS上积累了多年的本地基础架构。 但是，在React Native中，我们从空白的石板开始，不得不编写或创建所有现有基础架构的桥梁。 这意味着有时产品工程师需要一些尚不存在的功能。 那时，他们必须在他们不熟悉的平台上工作，并且不在他们项目的范围之内来构建它，或者在被创建之前被阻止。
We use Bugsnag for crash reporting on Android and iOS. While we were able to get Bugsnag generally working on both platforms, it was less reliable and required more work than it did on our other platforms. Because React Native is relatively new and rare in the industry, we had to build a significant amount of infrastructure such as uploading source maps in-house and had to work with Bugsnag to be able to do things like filter crashes by just those that occurred in React Native.
Due to the amount of custom infrastructure around React Native, we would occasionally have serious issues in which crashes weren’t reported or source maps weren’t properly uploaded.
Finally, debugging React Native crashes were often more challenging if the issue spanned React Native and native code since stack traces don’t jump between React Native and native.
我们使用Bugsnag在Android和iOS上进行崩溃报告。 虽然我们能够让Bugsnag通常在两个平台上工作，但它不太可靠，并且需要比在其他平台上做更多的工作。 由于React Native在行业中相对较新且稀少，因此我们必须构建大量基础设施，例如内部上传源地图，并且必须与Bugsnag合作才能执行诸如过滤器崩溃等事件 反应原生。
最后，如果问题跨越React Native和本机代码，调试React Native崩溃往往更具挑战性，因为堆栈跟踪不会在React Native和本机之间跳转。
Before React Native can render for the first time, you must initialize its runtime. Unfortunately, this takes several seconds for an app of our size, even on a high-end device. This made using React Native for launch screens nearly impossible. We minimized the first-render time for React Native by initializing it at app-launch.
在React Native首次可以渲染之前，您必须初始化其运行时。 不幸的是，即使在高端设备上，我们的尺寸也需要几秒钟的时间。 这使得使用React Native进行启动屏幕几乎是不可能的。 我们通过在应用程序启动时初始化React Native来缩短第一次渲染时间。
Initial Render Time
Unlike with native screens, rendering React Native requires at least one full main thread -> js -> yoga layout thread -> main thread round trip before there is enough information to render a screen for the first time. We saw an average initial p90 render of 280ms on iOS and 440ms on Android. On Android, we used the postponeEnterTransition API which is normally used for shared element transitions to delay showing the screen until it has rendered. On iOS, we had issues setting the navbar configuration from React Native fast enough. As a result, we added an artificial delay of 50ms to all React Native screen transitions to prevent the navbar from flickering once the configuration was loaded.
与原生屏幕不同，渲染React Native需要至少一个完整的主线程 - > js - >Yoga布局线程 - >主线程往返行程，然后才有足够的信息来第一次渲染屏幕。 我们看到iOS平均初始p90呈现280ms，Android平均440ms。 在Android上，我们使用通常用于共享元素转换的postponeEnterTransition API来延迟显示屏幕直到它被渲染。 在iOS上，我们遇到了问题，从React Native快速设置导航栏配置。 因此，我们为所有React Native屏幕过渡添加了50ms的仿真延迟，以防止配置加载后导航栏闪烁。
We still can’t ship a 64-bit APK on Android because of this issue.
We avoided using React Native for screens that involved complex gestures because the touch subsystem for Android and iOS are different enough that coming up with a unified API has been challenging for the entire React Native community. However, work is continuing to progress and react-native-gesture-handler just hit 1.0.
我们避免在涉及复杂手势的屏幕上使用React Native，因为Android和iOS的触摸子系统足够不同，以至于提出统一的API对整个React Native社区来说都具有挑战性。 然而，工作正在继续进行，并且react-native-gesture-handler刚刚达到1.0。
React Native has made some progress in this area with libraries like FlatList. However, they are nowhere near the maturity and flexibility of RecyclerViewon Android or UICollectionView on iOS. Many of the limitations are difficult to overcome because of the threading. Adapter data can’t be accessed synchronously so it is possible to see views flash in as they get asynchronously rendered while scrolling quickly. Text also can’t be measured synchronously so iOS can’t make certain optimizations with pre-computed cell heights.
React Native在像FlatList这样的第三方组件这方面取得了一些进展。 但是，它们远不及Android上的RecyclerView或iOS上的UICollectionView的成熟度和灵活性。 由于线程原因，许多限制难以克服。 适配器数据无法同步访问，因此可以看到视图闪烁，因为它们在快速滚动时异步呈现。 文本也无法同步测量，因此iOS无法使用预先计算的单元高度进行某些优化。（这个坑体会至深，如跗骨之蛆）
Upgrading React Native
Although most React Native upgrades were trivial, there were a few that wound up being painful. In particular, it was nearly impossible to use React Native 0.43 (April 2017) to 0.49 (October 2017) because it used React 16 alpha and beta. This was hugely problematic because most React libraries that are designed for web use don’t support pre-release React versions. The process of wrangling the proper dependencies for this upgrade was a major detriment to other React Native infrastructure work in mid-2017.
尽管大多数React Native升级都很微不足道，但有一些令人痛苦。 尤其是，使用React Native 0.43（2017年4月）至0.49（2017年10月）几乎是不可能的，因为它使用了React 16 alpha和beta。 这非常成问题，因为大多数专为Web使用而设计的React库不支持预发布React版本。 争夺此次升级的适当依赖关系的过程对2017年中期其他React Native基础架构工作造成重大损害。
In 2017, we did a major accessibility overhaul in which we invested significant efforts to ensure that people with disabilities can use Airbnb to book a listing that can accommodate their needs. However, there were many holes in the React Native accessibility APIs. In order to meet even a minimum acceptable accessibility bar, we had to maintain our own fork of React Native where we could merge fixes. For these case, a one-line fix on Android or iOS wound up taking days of figuring out how to add it to React Native, cherry picking it, then filing an issue on React Native core and following up on it over the coming weeks.
在2017年，我们进行了重大无障碍检修，我们投入了大量精力，确保残疾人士可以使用Airbnb预订符合其需求的清单。 但是，React Native 无障碍 API中有很多漏洞。 为了满足最低可接受的可无障碍性能，我们必须维护我们自己的React Native分支，我们可以在其中合并修补程序。 对于这些情况，Android或iOS上的一行修复需要数天时间才能确定如何将其添加到React Native，然后选择它，然后在React Native核心上提交问题并在接下来的几周内对其进行跟踪。
We have had to deal with a few very bizarre crashes that are hard to fix. For example, we are currently experiencing this crash on the @ReactPropannotation and have been unable to reproduce it on any device, even those with identical hardware and software to ones that are crashing in the wild.
SavedInstanceState Across Processes on Android
Android frequently cleans up background processes but gives them a chance to synchronously save their state in a bundle. However, on React Native, all state is only accessible in the js thread so this can’t be done synchronously. Even if this weren’t the case, redux as a state store is not compatible with this approach because it contains a mix of serializable and non-serializable data and may contain more data than can fit within the savedInstanceState bundle which would lead to crashes in production.
Android经常清理后台进程，但让他们有机会同步保存它们的状态。 但是，在React Native上，所有状态只能在js线程中访问，因此不能同步完成。 即使情况并非如此，作为状态存储的redux与此方法不兼容，因为它包含可序列化和不可序列化数据的混合，并且可能包含比saveInstanceState包中可能容纳的更多数据，这会导致崩溃产生。
This is part two in a series of blog posts highlighting our experiences with React Native and what’s next for mobile at Airbnb.