- React Native at Airbnb: The Technology
- By Gabriel Peal
- The Nuggets translation Project
- Permanent link to this article: github.com/xitu/gold-m…
- Translator: ALVINYEH
- Proofreader: ssshooter, LeeSniper
React Native: Technology part of Airbnb
The technical details
This is the second in a series of blog posts that will outline our experience with React Native and what’s next for Airbnb mobile.
React Native itself is a relatively new and rapidly iterating platform in various parts of Android, iOS, Web, and cross-platform frameworks. Two years later, it’s safe to say React Native was revolutionary in many ways. This is a paradigm shift for mobile devices, where we can monetize from multiple goals. However, its benefits are not without obvious pain points.
Technical advantages
cross-platform
The main benefit of React Native is that you can write code that runs on Both Android and iOS. With most features of React Native, you can share 95-100% of the code and 0.2% of the files required by different platforms (.android.js/.ios.js).
Unified Design Language System (DLS)
We developed a cross-platform design language called DLS. There are Android, iOS, React Native, and Web versions of each component. Having a unified design language makes it possible to write cross-platform code, which means that design, component names, and screens can be consistent across platforms. However, we are still able to make platform-appropriate decisions where applicable. For example, we use the native Toolbar on Android and the UINavigationBar on iOS, but we need to hide disclosure Indicators on Android because it does not comply with the Android platform design guidelines.
We chose to rewrite the component rather than encapsulate the native component. Because it would be more reliable to create a separate API for each platform, it would also reduce maintenance costs for Android and iOS engineers who might not know how to properly test the code changed in React Native. However, it does cause fragmentation between platforms where Native and React Native versions of the same component are out of sync.
React
React is the most popular Web framework for developers for a reason. That is, it is very simple but powerful, suitable for large code bases. The features we really like are:
- Components: The React component enforces separation of concerns through well-defined properties and states. React’s extensibility plays a big role.
- Simple life cycles: To put it simply, the Android and iOS life cycles are notoriously complex. The functional and responsive React components fundamentally solve this problem, so learning React Native is easier than learning Android and iOS.
- Declarative: React’s declarative features help keep our UI in sync with the base state.
The iteration speed
While developing with React Native, we were able to reliably use hot loading to test our changes on Android and iOS in just a second or two. Even the best Native apps can’t iterate as fast as React Native’s implementation. In the best case, the native compile time is 15 seconds, but for a full project build, it can be as high as 20 minutes.
Infrastructure investment
We developed extensive integration into our Native infrastructure, with many core components such as networking, internationalization, experimentation, shared element transformation, device information, account information, and many more packaged in a React Native API. These Bridges are some of the more complicated parts, because we want to encapsulate the existing Android and iOS apis into something consistent and canonical with React. While keeping these Bridges up to date through rapid iteration and development of new infrastructure, the infrastructure team’s input can simplify production efforts.
React Native can lead to a poor developer and user experience without a significant investment in the infrastructure. As a result, we don’t believe React Native can be implemented directly into existing applications without significant and sustained investment.
performance
Performance is one of React Native’s biggest problems. However, the chances of encountering this problem in practice are slim. Most of our React Native screens are as smooth as the original ones. We tend to think about performance in a single dimension. We often see mobile engineers refer to JS as “slower than Java”. However, in many cases, the business logic and layout of the main thread on the mobile side can improve rendering performance.
When we do find performance issues, most of them are caused by overrendering, which can be effectively addressed by using shouldComponentUpdate, removeClippedSubviews, and using Redux.
However, initialization and initial rendering times (outlined below) make React Native perform poorly when launching screens, Deep Links, and add TTI times when navigating between screens. Also, because the Yoga converts between the React Native component and the Native view, missing frames on the screen can be difficult to debug.
Redux
We used Redux for state management and found it very effective. Not only does it prevent the UI from being out of sync with state, but it also makes it easy to share data between screens. However, Redux is notorious for its templates and relatively difficult learning curve. We provide generators for some common templates, but it’s still one of the most challenging parts and confusing when working with React Native. It’s worth noting that these challenges aren’t unique to React Native.
Native support
Since everything in React Native can be bridged with Native code, we ended up building a lot of things we weren’t sure about when we started, such as:
- Shared element Transformation: We built a component that is supported by native shared element code on Android and iOS. This even applies to Native and React Native screens.
- Lottie: By encapsulating existing libraries on Android and iOS, we were able to make Lottie work in React Native.
- React Native network Stack: React Native uses our existing Native network stack and cache on both platforms.
- Other Core infrastructure: Just like the web, we encapsulated other existing Native infrastructure (internationalization, experimentation, etc.) so that it worked seamlessly in React Native.
Static analysis
We have a long history of using ESLint on the web, and we can use it this time too. But Airbnb was the first platform where Prettier was created. We found that it was effective in reducing PR headaches. Prettier is currently being studied by our Network infrastructure team.
We also used analytics to measure rendering times and performance to determine which screens were the top priority for investigating performance issues.
Because React Native is smaller and newer than our network infrastructure, it’s a great platform to test new ideas. Many of the tools and ideas we created for React Native are now being adopted by the Web.
Animated
Thanks to the React Native animation library, we were able to achieve smooth animations and even interactively driven animations such as parallax scrolling.
JS/React open source
Because React Native runs React and JavaScript, we can take advantage of a number of JavaScript projects, such as Redux, Reselect, Jest, etc.
Flexbox
React Native uses the Yoga to handle the layout. This is a cross-platform C library that handles layout calculations through the Flexbox API. Earlier, we were hit with Yoga’s limitations, such as its lack of aspect ratio, but have added them in future updates. Plus, fun tutorials like Flexbox Froggy make it even more enjoyable as you get started.
Collaborate with the Web
Late in the React Native exploration, we started building projects for the Web, iOS, and Android all at once. Since the Web also uses Redux, we found that a lot of code can be shared between the Web and the original console without any changes.
disadvantages
React Native is still immature
React Native is a little immature compared to Android or iOS. It’s new, it’s ambitious, and it iterates very quickly. While React Native works fine for the most part, there are situations where its immaturity can become apparent and things that are easy to do with Native become very difficult. Unfortunately, these conditions are difficult to predict and can take hours to days to resolve.
Maintain the React Native branch
Because React Native is still immature, we sometimes need to patch the React Native source code. In addition to feeding problems back to React Native, we also had to maintain a branch where we could quickly merge changes and upgrade versions. We’ve had to add about 50 commits to React Native in the last two years. This makes upgrading React Native a pain.
JavaScript tools
JavaScript is an untyped language. The lack of type safety is both difficult to scale and a point of contention for mobile engineers accustomed to typed languages who might otherwise be interested in learning React Native. We discussed the flow approach, but the implicit error messages led to a frustrating developer experience. We also looked at TypeScript, but integrating it into our existing infrastructure, such as Babel and Metro Bundler, was problematic. However, we continue to actively explore TypeScript on the Web.
refactoring
One side effect of untyped JavaScript is that refactoring is very difficult and error-prone. Renaming some properties, especially those with common names (such as onClick) or those passed through multiple components, can be a nightmare for accurate refactoring. To make matters worse, refactoring breaks down in production, not at compile time, making it difficult to statically analyze it properly.
JavaScriptCore inconsistent
One subtle and tricky aspect of React Native is that it needs to be executed on a JavaScriptCore environment. Here are the problems we encountered:
- IOS JavaScriptCore is available out of the box. That said, iOS is mostly consistent, and that’s fine with us.
- Android does not come with JavaScriptCore, so React Native provides it. By default, however, you get the old version. As a result, we had to bundle a new version of JavaScriptCore ourselves.
- React Native connects to Chrome developer tools for debugging. This is good because it is a powerful debugger. However, once the debugger is connected, all JavaScript will run in Chrome’s V8 engine. It works 99.9% of the time. However, in one instance, when we were using toLocaleString on iOS, we ran into problems and debugging only worked on Android. As it turns out, Android (not including JSC), unless you’re debugging it, in which case it’s running a V8 engine, will quietly fail. Not knowing these technical details can lead to days of painful debugging by product engineers.
React Native open source library
Learning platforms are difficult and time-consuming. Most people have a good understanding of one or two platforms. The React Native library has Native Bridges, such as maps and videos, that developers need to have the same understanding of all three platforms to implement. We found that most React Native open source projects were written by people who had one or two experiences. This leads to inconsistencies or unexpected errors 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 that are inconsistent with what the community expects.
Parallel infrastructure and work
We have years of native infrastructure on Android and iOS. However, in React Native, we started from a completely blank state and had to write or create Bridges to all existing infrastructures. This means that sometimes product engineers need features that don’t yet exist. In this case, they either work on an unfamiliar platform, build outside the scope of the project, or wait until someone builds the feature.
Collapse of the monitor
We use Bugsnag for Android and iOS crash reporting. While Bugsnag usually works on both platforms, it is unreliable and requires more work than on other platforms. Because React Native is relatively new and rare in the industry, we had to build a lot of infrastructure, such as internally uploaded source maps, and had to work with Bugsnag to implement events such as the React Native filter crash.
Due to the large number of custom infrastructures around React Native, there are occasional serious issues, such as unreported crashes or source maps not being uploaded correctly.
Finally, if the problem spans React Native and React Native code, debugging React Native crashes is often more challenging because stack traces cannot jump between React Native and React Native code.
The original bridge
React Native has a bridge API for communicating with React Native. While it worked as expected, it was cumbersome to write. First, it requires that all three development environments be set up correctly. We also had a lot of problems with incorrect typing from JavaScript. For example, integers are usually wrapped in strings, a problem that isn’t noticed until after the bridge. To make matters worse, sometimes iOS quietly fails and Android crashes. By the end of 2017, we were working on automatically generated bridge code from TypeScript definitions, but it was too late.
Initialization time
Before React Native renders for the first time, you must initialize its runtime. Unfortunately, even on high-end devices, our application takes a few seconds. So, it’s almost impossible to boot the screen with React Native. We shorten the first render time by initializing React Native when the application starts.
Initial render time
Unlike the Native screen, rendering React Native requires at least one full main thread -> JS -> Yoga layout thread -> before the main thread returns and then has enough information to render the screen for the first time. We can see that the average initial P90 rendering on iOS is 280ms, while Android requires 440ms. On Android, we use the postponeEnterTransition API normally used for shared element transformations to delay displaying the screen until it finishes rendering. On iOS, we ran into problems quickly setting the navigation bar configuration from React Native. Therefore, we added a 50ms simulation delay for all React Native screen transitions to prevent the navigation bar from flashing after the configuration loads.
App size
React Native also has a significant impact on application size. On Android, React Native (Java + JS + Native libraries such as Yoga + Javascript runtime) has a total size of 8MB per ABI. Using x86 and ARM (32 bits only) in one APK, the volume would be close to 12MB.
A 64 – bit
Due to this issue, we still can’t install a 64-bit APK on Android.
gestures
We avoided using React Native on pages that involved complex gestures, because the Touch subsystem on Android and iOS is so different that putting together a unified API was challenging for the entire React Native community. However, this work continues, with react-Native Mouth-Handler recently released in version 1.0.
The List is too long
React Native has made some advances in this area, such as the FlatList library. However, they are nowhere near as mature and flexible as RecyclerView on Android or UICollectionView on iOS. Many constraints are difficult to overcome because of the multi-process problem. Adapter data cannot be accessed synchronously, which causes views to flicker because they are rendered asynchronously during fast scrolling. In addition, text cannot be measured synchronously, so iOS cannot use pre-computed cell heights for certain optimizations.
To upgrade the React Native
While most React Native upgrades are trivial, some are downright painful. In particular, React Native versions 0.43 (April 2017) to 0.49 (October 2017) were almost unusable due to the use of React 16 Alpha and beta. This is a big problem because most React libraries designed for Web use don’t support previous React releases. The process of debating the proper dependencies for this upgrade has done significant damage to other React Native infrastructure efforts in 2017.
Auxiliary function
In 2017, we did an accessibility overhaul where we put a lot of effort into making sure that people with disabilities could use Airbnb to book their listings. However, React Native’s API for accessibility is full of bugs. To meet even the minimum acceptable accessibility bar, we had to maintain a branch of React Native to incorporate fixes. For these cases, a one-line fix on Android or iOS takes days to figure out how to add it to React Native, then cherry-pick, then submit the issue on React Native Core, and track it over the next few weeks.
Tricky crash
We had to deal with some very strange crashes that were difficult to solve. For example, we are currently experiencing this crash in the @ReactProp annotation and cannot reproduce it on any device, even if the device has the same hardware and software as the one that continues to crash.
SavedInstanceState cross-process on Android
Android often cleans up background processes, but gives them a chance to synchronize their state to the bundle. However, on React Native, all states are only accessible in the JS thread, so they cannot be synchronized. Even if this is not 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 types of data than is contained in the saveInstanceState package, which can crash in a production environment.
This is the second in a series of blog posts focusing on our experience with React Native and what’s next for Airbnb mobile.
- Part I: React Native in Airbnb
- Part TWO: Technical details
- Part three: Building a cross-platform mobile team
- Part 4: Decisions made on React Native
- Part five: What’s next on mobile
If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.
The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.