WORA: Write Once, Run Anywhere: The world of REACT
React's philosophy is geared towards creating a unified development experience across platforms.
As a development team lead, I can hire only REACT talented developers, not needing to hire (a) web developers, (B) IOS developers, (3) android developers
Let’s dig into the foundational building blocks:
React is a JavaScript library for building user interfaces, primarily targeting web applications. It does not natively interact with hardware features like camera, GPS, or contacts list. React Native is a separate framework from React, and it's used for building mobile applications that can run on both iOS and Android.
React Native code is written in JavaScript and allows access to platform-specific APIs and hardware features, but it does not use Objective-C, Java, or JNDI directly. Instead, it uses a bridge system to communicate with native code when necessary.
React Native is a framework that allows developers to build mobile applications using JavaScript and React. One of its most powerful features is the ability to interact with native platform components and APIs. This is where the "bridge" system comes into play. The Bridge Concept
The bridge is a core part of React Native's architecture that facilitates communication between the JavaScript realm and the native realm. Here's how it works:
The JavaScript realm is where your React Native JavaScript code runs, typically within a JavaScript engine like JavaScriptCore (which is used on both iOS and Android). The native realm is the part of your app that runs in native code, such as Swift or Objective-C on iOS and Java or Kotlin on Android. When you're building a React Native app, much of your application logic and UI components are written in JavaScript, but at some point, you might need access to functionality that only native code can provide—like accessing the device's camera or processing gestures at a high frame rate.
Communication via the Bridge
When a React Native app needs to execute native code (such as accessing hardware features or performing an action that requires native APIs), the JavaScript code sends a message across the bridge to the native realm.
The bridge translates this message from the JavaScript thread to the native thread, performs the necessary native operations, and if needed, sends back a response to the JavaScript thread.
Crossing the Bridge: Connecting REACT Library API calls to Device Hardware
The "bridge" in React Native serves as an integral mediator that connects the JavaScript world, where the React library APIs operate, with the native device hardware functionality.
Let's delve into how this connection works, allowing developers to create rich, native applications using the popular React paradigm.
How the Bridge Operates
React Native's bridge system is a bidirectional communication channel that allows JavaScript threads and native threads to send messages to each other. This is how React Native marries the JavaScript code written by developers with the actual native controls and device hardware.
Platform-Specific Bridges in React Native: IOS and Android
The bridge in React Native differs for iOS and Android because it interacts with different native hardware and operation system call environments.
Here's how it works for each platform:
iOS Bridge: On iOS, the bridge connects JavaScript code running in JavaScriptCore (the JavaScript engine that ships with iOS) to the Objective-C or Swift code that interacts with iOS APIs and components. To build and deploy an app for iOS, you would typically need to use a MacOS machine with Xcode installed, as Xcode provides the necessary tooling, simulators, and the ability to sign applications for deployment. Android Bridge: On Android, the bridge facilitates communication between JavaScript running in JavaScriptCore or Hermes (a JavaScript engine optimized for mobile apps) and the Java or Kotlin code using the Android API and components. Developers can build and deploy for Android using a variety of operating systems, with Android Studio being the standard development environment. While the react native bridge allows communication between JavaScript and the respective native languages of each platform, it doesn't mean that developers typically write Objective-C, Swift, Java, or Kotlin code when creating a standard React Native app. Developers call REACT NATIVE Library APIs which do the underlying communications with the target platform hardware memory addresses and Operation System process calls.
Instead, the bridge and the native code that make up the core React Native APIs are already implemented and exposed to JavaScript as modules and components.
Developers call REACT NATIVE Library APIs which do the underlying communications with the target platform hardware memory addresses and Operation System process calls.
React Native abstracts away many of the complexities involved in directly handling platform-specific hardware memory addressing and OS process calls. Let's expand on that:
Abstraction Layer
React Native provides a high-level abstraction layer that allows developers to write JavaScript code that, under the hood, interacts with the platform-specific features and hardware. Developers typically do not need to be concerned with memory addresses or direct system process calls.
React Native Library APIs
The React Native framework's APIs are designed to give developers access to platform-specific capabilities such as the device camera, file system, location services, and other hardware-related functions. When developers invoke these APIs:
React Native translates these JavaScript API calls into corresponding native actions through the React Native bridge, as discussed earlier. The bridge communicates with native modules that handle the actual interaction with the OS and hardware of the device. It's the native modules that interact with the memory addresses and system processes to accomplish tasks. They expose a JavaScript interface for the React Native environment to interact with. The Role of the Native Modules
Native modules are platform-specific code pieces (in Objective-C, Swift for iOS, and Java or Kotlin for Android) that React Native invokes as required. They perform tasks by issuing system API calls and can return results back to the JavaScript realm via the bridge.
Operating System & Hardware Communication
Here's what happens when a React Native API is called to interact with hardware:
JavaScript Call: React Native library API is called from the JavaScript code. Bridge Serialization: The call is serialized and sent over the bridge to the native side. Native Module Execution: The native module in Objective-C/Swift or Java/Kotlin receives the call, communicates with the OS using native APIs, which in turn interact with the device's hardware. Response Handling: Any responses or events from the hardware/OS are sent back to the JavaScript side through the bridge, often as serialized data that gets deserialized in the JavaScript realm. Advantages for Developers
With React Native, developers do not need to be versed in platform-specific languages or hardware interactions. They rely on React Native’s abstraction to handle such communications, allowing them to focus on building the app's logic and user interface. In cases where the built-in React Native APIs do not cover a required functionality, developers do have the option to write their own native modules or use third-party modules to achieve the desired functionality.
In summary, React Native significantly simplifies the process of building cross-platform mobile applications by allowing developers to interact with hardware and system processes through a rich set of pre-made APIs and, when necessary, to extend capabilities through native code.
Writing Native Modules
However, developers can write their own native modules and components when they need functionality that isn't available in React Native's core APIs.
Here's an outline of how this works:
Create Native Module: If, for instance, you are writing a native module for iOS, you would write Objective-C or Swift code that exposes certain functionalities to JavaScript. This might involve creating classes that inherit from RCTBridgeModule and using React Native macros to expose methods to JavaScript. Use Native Module in JavaScript: Once you have your native module, you call it from your JavaScript code using the NativeModules object provided by React Native. The bridge handles the message passing between your JavaScript code and the native module. Deploy on Device: To run the app, you would compile the iOS app using Xcode on a MacOS system, which packages the JavaScript code along with the native code into an executable application. In both iOS and Android development with React Native, the bridge works behind the scenes to abstract away the specifics of each platform, allowing developers to focus more on JavaScript and less on the native side. When developers do need to dig into platform-specific code, they utilize the bridging mechanism to hook into native features from their JavaScript code.
Conclusion
To recap, while the bridge does involve platform-specific implementations, most React Native developers interact with it through high-level APIs rather than writing direct bridge code themselves. Specialized native development is only typically needed when extending React Native's capabilities beyond what is provided out of the box.
Making API Calls from React Native to Device Hardware
When building a React Native application, developers will mostly interact with JavaScript objects and functions provided by React and React Native APIs. However, to handle device-specific operations like accessing the camera, GPS, or accelerometer, they need to send messages across the bridge to native modules that can perform these actions.
The Process of Connecting to Device Hardware
Here's a simplified high-level view of the process:
Invoke Device Capabilities: A React Native component makes a call to a method provided by a native module, such as taking a photo or fetching location data. Serialization: The JavaScript thread sends the call across the bridge, where it gets serialized into a format that can be efficiently transferred. Deserialization and Native Execution: The native thread receives the message, deserializes it, and executes the corresponding native function to interact with the device hardware. Callbacks and Promises: The result of the native operation is often handled by a callback function or a Promise in JavaScript. Once the native thread has completed the operation, it sends back a message over the bridge to JavaScript, which is then turned into a response that the JavaScript code can work with. Update React Native UI: The state of the React Native app may be updated based on the response from native code, triggering a re-render if necessary to reflect changes in the UI. Example of a Device Hardware Interaction
Here is a basic example showing how a React Native component might interact with device hardware through the bridge:
import { CameraRoll, Button } from 'react-native';
export default function PhotoSaver() {
const savePhoto = async () => {
try {
// This method invokes a native module method to save an image to the Camera Roll
const savedPhoto = await CameraRoll.save(tag, { type, album });
console.log('Photo saved', savedPhoto);
} catch (error) {
console.error('Error saving photo', error);
}
};
return (
<Button title="Save Photo" onPress={savePhoto} />
);
}
In this example, CameraRoll.save is a method exposed by a native module that communicates with the device's photo storage. When the button is pressed, savePhoto is called, which in turn interacts with the native realm via the bridge to perform the requested action.
Bridging Performance Considerations
While powerful, the bridge system can be a bottleneck due to its asynchronous and serialized nature. Operations performed on the bridge can lead to performance issues if not managed correctly, especially if there is a large volume of data or frequent bridging required.
Future Improvements with TurboModules and the New Architecture
To address these performance concerns, the React Native team is working on the new architecture that includes TurboModules and the JSI (JavaScript Interface).
These improvements aim to streamline the interaction between JavaScript and native code, reduce serialization overhead, and make direct synchronous calls when necessary, leading to more performant interactions with device hardware.
In conclusion, the bridge system in React Native plays a critical role in connecting the high-level React library API calls to the low-level device hardware functionalities, allowing developers to build cross-platform native applications with the same ease and agility as web development.
The messages sent across the bridge are serialized, sent as JSON, and then deserialized on the other side. This means there is some overhead to every single interaction between JavaScript and native code. As such, one of the main considerations in React Native performance is minimizing the number of bridge crossings, or at least making sure that they are efficient and not blocking either thread for long periods.
It's also worth noting that the bridge works asynchronously. JavaScript can send a message to native, or native can send a message to JavaScript, but neither expects an immediate response. This design allows for better performance but requires a different way of thinking about data flow and event handling in your app.
Native Modules and Native Components
Native Modules: You can create your own native modules for parts of your app that need to perform complex calculations, handle sensitive data, or use APIs that are not accessible from JavaScript. Native Components: Similarly, if there are native UI components that aren't provided by React Native, you can create them yourself and manage their properties, events, and children from JavaScript using the bridge. This allows you to take full advantage of the platform's capabilities and user interface elements. Evolution of the Bridge
The React Native team is working on a new architecture known as "Fabric" that aims to improve the performance of the bridge system by allowing JavaScript to directly invoke methods in native code without requiring the serialization/deserialization process. This represents a significant step forward in React Native development.
In summary, the bridge system in React Native is a powerful and flexible system that allows you to build high-quality, cross-platform mobile applications using the familiar React paradigm. It's the secret sauce that lets developers enjoy the benefits of writing code once and running it on both iOS and Android, while still being able to tap into the platforms' full capabilities.
The “Write Once, Run Anywhere” philosophy is closely associated with React Native. React (for the web), although very reusable, requires different considerations than React Native when working with device capabilities and rendering. Now, let's discuss the foundational building blocks:
(1) The React Library of APIs
React is a declarative, component-based JavaScript library for building efficient and flexible user interfaces. It provides a collection of APIs that allow developers to define components as classes or functions. These components can manage their own state using this.state or the useState hook and can receive data from their parent components through props.
Some of the key APIs and features of React include:
React Elements: The smallest building blocks of React apps, representing the description of what to display. React Components: Independent and reusable bits of code that return React elements from their render method or function body. State & Lifecycle: APIs that allow components to create interactive and dynamic content by managing the data that changes over time and responding to lifecycle events. Hooks: Functions that let functional components “hook into” React’s state and lifecycle features (e.g., useState, useEffect). (2) The React DOM Rendering Library
For web applications, React works alongside ReactDOM, which is a complementary library responsible for rendering components to the DOM (Document Object Model). ReactDOM updates the browser's DOM efficiently by calculating the differences (reconciliation) between the current page and the changes that a component's render output may cause, hence effectively applying the minimal set of changes necessary to update the web page.
React Native and Platform-Specific Code
React Native extends React's philosophy of declarative components to mobile app development, allowing you to write components in JavaScript and render them using native views. It's not written in Objective-C or Java, but provides JavaScript interfaces to access platform-specific native capabilities.
React Native uses a bridge system that allows the JavaScript code to communicate asynchronously with the native code, enabling access to features like the camera, contacts list, etc. Developers use special APIs and components provided by React Native to interact with hardware features and design the user interface using styles that align with the native look and feel on both iOS and Android.
The Unified Development Experience
With React for web development and React Native for mobile applications, development teams can leverage a shared knowledge base in JavaScript and React's design patterns. This does indeed reduce the necessity of hiring specialized web, iOS, and Android developers to some extent, as many skills are transferable between web and native development when using React and React Native.
However, while React and React Native share similar philosophies and some code, they are distinct. Teams often still need expertise in native development for complex tasks and performance optimization, particularly for large-scale and highly customized applications. The React ecosystem is about component reusability and writing declarative code, which provides benefits for maintainability and productivity, but integration with platform-specific features may sometimes require developers familiar with native languages and ecosystems.