React as a UI Runtime
An in-depth description of the React programming model.
React as a UI Runtime
February 2, 2019
Most tutorials introduce React as a UI library. This makes sense because React is a UI library. That’s literally what the tagline says!
[React homepage screenshot: "A JavaScript library for building user interfaces"]
I’ve written about the challenges of creating user interfaces before. But this post talks about React in a different way — more as a programming runtime.
This post won’t teach you anything about creating user interfaces. But it might help you understand the React programming model in more depth.
Note: If you’re learning React, check out the docs instead.
⚠️
This is a deep dive — THIS IS NOT a beginner-friendly post. In this post, I’m describing most of the React programming model from first principles. I don’t explain how to use it — just how it works.
It’s aimed at experienced programmers and folks working on other UI libraries who asked about some tradeoffs chosen in React. I hope you’ll find it useful!
Many people successfully use React for years without thinking about most of these topics. This is definitely a programmer-centric view of React rather than, say, a designer-centric one. But I don’t think it hurts to have resources for both.
With that disclaimer out of the way, let’s go!
Host Tree
Some programs output numbers. Other programs output poems. Different languages and their runtimes are often optimized for a particular set of use cases, and React is no exception to that.
React programs usually output a tree that may change over time. It might be a DOM tree, an iOS hierarchy, a tree of PDF primitives, or even of JSON objects. However, usually, we want to represent some UI with it. We’ll call it a “host tree” because it is a part of the host environment outside of React — like DOM or iOS. The host tree usually has its own imperative API. React is a layer on top of it.
So what is React useful for? Very abstractly, it helps you write a program that predictably manipulates a complex host tree in response to external events like interactions, network responses, timers, and so on.
A specialized tool works better than a generic one when it can impose and benefit from particular constraints. React makes a bet on two principles:
Stability. The host tree is relatively stable and most updates don’t radically change its overall structure. If an app rearranged all its interactive elements into a completely different combination every second, it would be difficult to use. Where did that button go? Why is my screen dancing?
Regularity. The host tree can be broken down into UI patterns that look and behave consistently (such as buttons, lists, avatars) rather than random shapes.
These principles happen to be true for most UIs. However, React is ill-suited when there are no stable “patterns” in the output. For example, React may help you write a Twitter client but won’t be very useful for a 3D pipes screensaver.
Host Instances
The host tree consists of nodes. We’ll call them “host instances”.
In the DOM environment, host instances are regular DOM nodes — like the objects you get when you call document.createElement('div'). On iOS, host instances could be values uniquely identifying a native view from JavaScript.
Host instances have their own properties (e.g. domNode.className or view.tintColor). They may also contain other host instances as children.
(This has nothing to do with React — I’m describing the host environments.)
There is usually an API to manipulate host instances. For example, the DOM provides APIs like appendChild, removeChild, setAttribute, and so on. In React apps, you usually don’t call these APIs. That’s the job of React.
Renderers
A renderer teaches React to talk to a specific host environment and manage its host instances. React DOM, React Native, and even Ink are React renderers. You can also create your own React renderer.
React renderers can work in one of two modes.
The vast majority of renderers are written to use the “mutating” mode. This mode is how the DOM works: we can create a node, set its properties, and later add or remove children from it. The host instances are completely mutable.
React can also work in a “persistent” mode. This mode is for host environments that don’t provide methods like appendChild() but instead clone the parent tree and always replace the top-level child. Immutability on the host tree level makes multi-threading easier. React Fabric takes advantage of that.
As a React user, you never need to think about these modes. I only want to highlight that React isn’t just an adapter from one mode to another. Its usefulness is orthogonal to the target low-level view API paradigm.
React Elements
In the host environment, a host instance (like a DOM node) is the smallest building block. In React, the smallest building block is a React element.
A React element is a plain JavaScript object. It can describe a host instance.
// JSX is a syntax sugar for these objects.
// <button className="blue" />
type: 'button',
props: { className: 'blue' }
A React element is lightweight and has no host instance tied to it. Again, it is merely a description of what you want to see on the screen.
Like host instances, React elements can form a tree:
// JSX is a syntax sugar for these objects.
// <dialog>
// <button className="blue" />
// <button className="red" />
// </dialog>
type: 'dialog',
props: {
children: [{
type: 'button',
props: { className: 'blue' }
}, {
type: 'button',
props: { className: 'red' }
(Note: I omitted some properties that aren’t important to this explanation.)
However, remember that React elements don’t have their own persistent identity. They’re meant to be re-created and thrown away all the time.
React elements are immutable. For example, you can’t change the children or a property of a React element. If you want to render something different later, you will describe it with a new React element tree created from scratch.
I like to think of React elements as being like frames in a movie. They capture what the UI should look like at a specific point in time. They don’t change.
Entry Point
Each React renderer has an “entry point”. It’s the API that lets us tell React to render a particular React element tree inside a container host instance.
For example, React DOM entry point is ReactDOM.render:
ReactDOM.render(
// { type: 'button', props: { className: 'blue' } }
<button className="blue" />,
document.getElementById('container')
When we say ReactDOM.render(reactElement, domContainer), we mean: “Dear React, make the domContainer host tree match my reactElement.”
React will look at the reactElement.type (in our example, 'button') and ask the React DOM renderer to create a host instance for it and set the properties:
[...]