How the React Native bridge works and how it will change in the near future
Tags: reactnative, react, javascript
Published: 2020-08-09 13:17:14

A React Native app is made up of two sides, the JavaScript side and the native side. The native side could be Objective-C/Swift for iOS or Java/Kotlin for Android (not to mention the other platforms for React Native like web and desktop). The React Native Bridge allows the native code and the javascript code to talk to each other. Without the bridge, there is no way for the native code to send any information to the JavaScript code and vise versa.

## How does the bridge work?

You tap your app icon to open your app and the OS creates a Main Thread(aka a UI thread) and assigns it to your app. The main thread spawns the JavaScript thread and the shadow thread (or called the shadow tree). The shadow tree's job is to calculate layouts defined on the JS side and send that information to the native side. Views are written in JavaScript, calculated in the Shadow thread, and sent to the UI thread.

*If you are more of a visual learner I created a video explaining how the bridge works, watch here*: https://youtu.be/TU_kTuz2i9Y

## Sending data

Okay, so we know how layouts are defined at startup but what happens after the app is up and running? what if we want to disable a button? Does that information get sent along the bridge?

To disable a button we can set a property on the JS side which will be sent over the bridge as a serialized JSON object. Updates to native views are batched together and sent over to the native side at the end of each iteration of the event loop. 

In addition to passing properties around, we can pass a function that will run JavaScript code as a reaction to some event on the native side (like a button press). We write this callback in JavaScript, which gets serialized and sent to the native side. When the button is pressed the native event is sent back to the JS realm and the callback is executed. Now, you can also send events from the native side to the JS side directly without using a callback. The problem is if you originate this interaction on the native side you don't know who is listening on the JavaScript side, which can trigger unwanted actions and make your code harder to debug. It makes more sense to use callbacks or promises from the JS side unless to have a specific reason not to.

## Performance

The majority of the time everything runs smoothly but like a real-life bridge, you can occasionally get traffic jams. When you have a big list of items and start scrolling really fast you might see a blank screen before the rest of the items are shown. This is because the onScroll native event is being sent to the JavaScript thread, the JavaScript thread sends the new layout information to the shadow thread, the shadow thread calculates the layout and sends it back to the native side. When scrolling fast you get a bunch of these events which causes a traffic jam across the bridge. You can try to avoid this by pre-calculating layouts so you can cross the bridge fewer times.  

You get the same performance problems when running complex animations too. For example, typically a device runs at 60 frames per second which gives it that smooth lifelike feel. After one "frame" is shown you have roughly 16 ms to run code and display the next frame. If you take too long then the frame is dropped and your app appears unresponsive or laggy. With complex animations, it's best to stay on the UI thread as much as possible.

## Future

So that's how the bridge works. Facebook is well aware of some of the performance hits of using the bridge. They are working on a whole new architecture for React Native that eliminates the requirement to use the bridge.

They are implementing something called the JavaScript Interface, or JSI, which will sit between the JavaScript code and the JavaScript engine. Currently, React Native runs on JavaScriptCore which already exists on iOS (it runs the Safari browser) but JavaScriptCore has to be shipped with an Android app and has performance issues on low-end or older android phones. But now we have Hermes, which is a light JavaScript engine optimized for running React Native on Android. With JSI, we can more easily swap out JavaScript engines in the future. As a side note, when you run React Native in debug mode it actually runs in chrome, which uses the V8 engine. In rare cases, that can lead to some inconsistencies between how JavaScript runs in debug vs production.

That's nice and all but the real benefit to using JSI is the JavaScript side and the native side can finally talk to each other. You will not have to serialize a JSON message and send it over the bridge to talk to the other side. 

As of the time of this article, JSI is mostly stable but still needs some improvements before being production-ready.

All native modules used by the JavaScript code have to be initialized on startup, which can impact performance. As a part of the new React Native architecture, that will change. The JavaScript code will only load each module when it's actually needed. No more using the old bridge because the JavaScript code can hold a direct reference to it. This is going to improve startup times for apps that use a lot of native modules. These new modules are called TurboModules and they seem to be mostly working but haven't been officially released. Facebook is using TurboModules internally and some open source libraries have started using TurboModules, namely react-native-reanimated v2.