React uses a declarative paradigm to render UI and keep it updated. This means you simply declare what your component's UI should look like at any given point, and React will handle efficiently updating the DOM to match that state.
The Basic Rendering Process
At its core, React rendering works like this:
Components return React elements describing what should appear on the screen
React takes those elements and uses them to construct a "tree" of React elements
This tree is stored in memory and React uses it to figure out how to efficiently update the UI
When the tree updates, React will compare the new tree with the previous one and calculate the minimal set of DOM mutations needed to update the UI
React will batch these DOM mutations and apply them all at once to keep the real DOM updated
So in summary, React rendering involves:
Describing UI as React elements
Using these to construct a React element tree
Updating this tree when the state changes
Comparing tree differences to extract minimal DOM mutations
Batching DOM mutations to keep the real DOM in sync
Declarative vs Imperative Rendering
An important distinction in React is between declarative and imperative rendering approaches.
Imperative code explicitly tells the computer each step required to render the UI. Declarative code instead describes the desired end result, letting the computer handle the steps needed.
React uses a declarative paradigm. You don't worry about exactly how to update the real DOM. You simply declare the UI as a function of state, and React handles the rendering details.
For example, this imperative pseudo-code says "First make a new DOM node, then set its text, then insert it into the DOM":
const textNode = document.createElement('div');
textNode.innerText = 'Hello World';
document.body.appendChild(textNode);
The React declarative equivalent simply returns the element we want:
return <div>Hello World</div>;
React takes care of creating, updating, and inserting this div for us. This simplifies our code and makes it more predictable.
React Elements as Objects So what exactly are these React elements we keep returning from components?
They may look like HTML, but these elements are actually JavaScript objects that represent a component instance and its desired UI.
React Elements as Objects
So what exactly are these React elements we keep returning from components?
They may look like HTML, but these elements are actually JavaScript objects that represent a component instance and its desired UI.
For example this JSX element:
<MyComponent />
Is synthesized into an object like this:
{
type: MyComponent,
props: {}
}
So these React elements are just lightweight descriptions of what we want to render, not the actual DOM nodes themselves.
This is why React can easily represent an entire UI tree with elements, without needing to immediately create DOM nodes and insert them.
The React Element Tree
As mentioned earlier, React uses our returned elements to construct a tree of React elements.
For example, given this component structure:
<App>
<Header />
<Content>
<Article />
<Sidebar />
</Content>
<Footer />
</App>
The resulting React element tree would look like:
{
type: App,
props: {},
children: [
{
type: Header,
props: {},
children: []
},
{
type: Content,
props: {},
children: [
{
type: Article,
props: {},
children: []
},
{
type: Sidebar,
props: {},
children: []
}
]
},
{
type: Footer,
props: {},
children: []
}
]
}
React uses this tree to compute minimal DOM updates when state changes cause the tree to update.
Scheduling Updates
When state changes in a component, it triggers a re-render of that component and its children.
React will then look at the new React element tree and identify which DOM nodes need to be updated.
However, React does not immediately update the DOM. Instead, it batches and defers DOM mutations to keep everything fast and prevent wasteful re-renders.
This batching is related to another React concept called the "virtual DOM". The virtual DOM is essentially a JavaScript representation of the actual DOM tree.
By batching DOM mutations, React can process most updates within the faster virtual DOM first before updating the real DOM.
Here is a simplified overview of how the batching process works:
When state changes, React marks the component as "dirty" to be re-rendered
The next update cycle begins, starting at the root
React reconciles all dirty components and their children, constructing a new React element tree
DOM mutations are captured as it diffs the new tree with the previous
These DOM mutations are batched and applied all at once
Layout/paint occurs as the real DOM updates
The component tree returns to idle/clean state
So in summary, React uses a declarative paradigm where we simply describe our UIs as functions of state. React handles constructing element trees, diffing them, and batching DOM mutations to keep things fast.
This is the core of how React rendering works under the hood. The declarative model paired with batching/virtual DOM reconciliation enables React to minimize DOM operations and create a very performant UI architecture.