React Native is an excellent entering point to app development for a web developer, especially with React experiences.
However, it does not mean that React Native is simple. You can write apps with your familiar language, JavaScript, but it certainly requires an overall understanding of iOS and Android platforms. This article is a summary of my learning process on the React Native architecture and the ecosystem from the web developer point of view.
The Execution Environments
React runs on the JavaScript runtime environment. For the web, it is a web browser. There is a single JavaScript thread, and it uses web APIs implemented natively in the browser.
It is important to understand how the callbacks work between the main JS thread and Web APIs, especially when using asynchronous functions. This interaction between JavaScript engine and native APIs are also vital to understand how React Native behaves in its environment.
Threads in React Native
There are three key threads in React Native execution environment, JavaScript thread, Native main thread and the background thread to handle Shadow Node.
In the current architecture, the communication between these threads happens over the library called "bridge".
The React Native team is actively working on the major architecture upgrade and this article gives a great overview of why and how the change is needed.
I don't get into the details, but the basic understanding of the current and future architecture helps to model your application, especially to separate the concerns.
React and React Native
As you can see above, React Native covers a significantly broader area than React itself.
React for the web looks rather intuitive as the key concept, the Virtual DOM, implies the browsers' HTML dom rendering. But in-fact, the Virtual DOM is not tied to the HTML DOM (Document Object Model). The Virtual DOM in React is more of a programming concept or a pattern than a specific technology.
It provides an abstraction for the declarative UI. The virtual representation of a UI is kept in memory and synced with the external UI libraries. This process is called reconciliation.
You can read a good explanation of the React Fiber Architecture here.
Reconciliation versus rendering
The DOM is just one of the rendering environments React can render to, the other major targets being native iOS and Android views via React Native. (This is why "virtual DOM" is a bit of a misnomer.)
The reason it can support so many targets is because React is designed so that reconciliation and rendering are separate phases. The reconciler does the work of computing which parts of a tree have changed; the renderer then uses that information to actually update the rendered app.
This separation means that React DOM and React Native can use their own renderers while sharing the same reconciler, provided by React core.
Fiber reimplements the reconciler. It is not principally concerned with rendering, though renderers will need to change to support (and take advantage of) the new architecture.
React Native Components and React Navigation
React Native provides its own UI abstraction layer over iOS and Android platforms. React Native core and native components invoke the native views so that you can write the smartphone app UI with JavaScript, instead of Kotlin/Java or Swift/Objective-C.
Core Components and Native Components
The native components cover comprehensive native UI elements, but you still need to write a lot of code to simulate the native user experience such as tab navigation. That's where React Navigation comes in.
React Navigation is a pure JavaScript library which does not include any native code. It is built on the other native libraries such as Reanimated, Gesture Handler, and Screens to implement the common app navigation patterns.
It provides the best practice of how to structure and navigate the app screens, which is one of the most confusing parts when you come from a web development background.
My advice is to stick to the basic navigation patterns until you are confident, and you can implement your custom navigators on top of React Navigation once you have the good overviews. I would also prefer to place navigators and screens into the dedicated directories to clearly separate them from the other components.
Thinking in React
Despite the difference in the UI implementations, the thought process of building a new application stays the same as "Thinking in React" way.
- Start With A Mock
- Break The UI Into A Component Hierarchy
- Build A Static Version in React
- Identify The Minimal (but complete) Representation Of UI State
- Identify Where Your State Should Live
- Add Inverse Data Flow
Hooks and Functional Component
React 16.8.0 introduced Hooks in 2019, and it was a big paradigm shift. The React team expects Hooks to replace all the Class Component use cases, and the popular libraries have already migrated toward this direction, for example, React Navigation 5.0 and React Redux 7.1.0 introduced their Hook APIs.
Conceptually, React components have always been closer to functions, and "Thinking in React" way became more straight forward with Hooks.
The motivation behind Hooks explains the benefits:
Hooks let you split one component into smaller functions based on what pieces are related (such as setting up a subscription or fetching data), rather than forcing a split based on lifecycle methods. You may also opt into managing the component's local state with a reducer to make it more predictable.
Component Side Effects and Closure
Based on the prototype inheritance, JavaScript functions are said to be the first-class citizens which means that they can be:
- assigned to variables
- used as functional parameters
- returned from a function
These are equally applied to React's Functional Component. JavaScript's Closure is also an essential element when using Hooks.
A closure is a combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function's scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time. (MDN Web docs: Closures)
Like this example in Hooks FAQ, it is very important to understand when the closure is created in the component lifecycle and use the stable function instead of unstable state variables within Hooks.
Note: Unlike the setState method found in class components, useState does not automatically merge update objects. You can replicate this behaviour by combining the function updater form with object spread syntax:
React also provides Context API to share data that can be considered "global" for a tree of React components, such as the current authenticated user, theme, or preferred language.
Referential Transparency and Static Type Checking
JavaScript is multi-paradigm, Object-Oriented Programming and Functional Programming, and React has been inherited the strength of both. But with Hooks, I feel that it is more opinionated toward the functional programming.
The key feature of React is the composition of components. Components written by different people should work well together. It is important to us that you can add functionality to a component without causing rippling changes throughout the codebase. (React Design Principles)
By extracting side effects from a React component, it becomes more predictable. You can expect the component to render the same output if the input is the same. In more specific words, it can gain referential transparency, or be idempotent.
In practice, the referential transparency should be assured by the static type checking and sufficient unit testing.
Static type checkers and linters, my preference is TypeScript and ESLint, make the development experience more confident and solid as they can identify certain types of problems before you even run your code.
Although the configuration could be a bit cumbersome when you start a new project, it helps you and your team to be much more productive. I don't see any reason not to use them in 2020.
Component Tests
A declarative component is easier to write the test as you can focus on the pure interaction and rendering of the component.
With Hooks, you can extract stateful logic from a component so it can be tested independently and reused. Hooks allow you to reuse stateful logic without changing your component hierarchy. (The motivation behind Hooks)
I believe that React Native Testing Library is now a defacto testing library for React Native. It integrates closely with Jest and provides clear testing methodologies along with the popular libraries such as React Navigation and Redux.
React Test Renderer is developed alongside React core. It renders React components to pure JavaScript objects, without depending on the DOM or a native mobile environment.
React Native Testing Library (or RNTL) is built on top of React Test Renderer. It adds useful APIs such as to render ( to getByText, queryAllByA11yRole, β¦), fireEvent, waitFor, and act. It is opinionated to focusing on user experience and accessibility.
React Hooks Testing Library to write test custom hooks that are not directly tied to a component or complex that is difficult to test through component interactions.
Conclusion
I understand that there has been and will be always a debate between React Native vs Native App development.
But as a JavaScript developer, React and React Native itself is a very interesting and inspiring framework to learn. It is leveraging the JavaScript's capability at the maximum level and packed with the best practices.
Through the learning process, I felt like gaining a more in-depth understanding of JavaScript language itself. I hope that this article may convey excitement.
Top comments (5)
As a React developer I think that learning React Native would be the obvious choice if i wanted to create mobile apps. However i keep thinking that writing native code would be better in the long run. You going to make many cool apps using that stack?
Hi Andrew,
I will definitely learn iOS and Android native development as that knowledge would be useful anyway if I were only using React Native.
Once I am confident with those platforms, I may switch to the native development, but I would prefer to ship something before with my already familiar technology stack.
I agree with you. But sometimes is easier to develop a mobile app with React native and deliver an MVP, to validate the product.
Nice article!
Cool stuff! Featured in React Native Now #70