How React Works Under the Hood
React is a powerful JavaScript library for building user interfaces, but understanding how it works internally can help you write better, more performant applications. This document explains the core mechanisms that make React efficient and powerful.
The React Workflow Overview
React follows a systematic workflow that transforms your JSX code into actual DOM updates:
- JSX → 2. React.createElement → 3. Virtual DOM → 4. Diffing → 5. Real DOM
Let's dive deep into each step of this process.
1. JSX (JavaScript XML)
JSX is a syntax extension for JavaScript that allows you to write HTML-like code within your JavaScript files. While it looks like HTML, JSX is actually syntactic sugar that gets transformed into JavaScript function calls.
What JSX Looks Like:
const element = <h1 className='greeting'>Hello, World!</h1>;
Key Points:
- JSX is not valid JavaScript by itself
- It must be transpiled (usually by Babel) before browsers can understand it
- JSX expressions can contain JavaScript code within curly braces
{} - JSX elements are case-sensitive (lowercase for HTML elements, uppercase for React components)
2. React.createElement
During the build process, JSX gets transformed into React.createElement() function calls. This is the bridge between your declarative JSX and React's internal representation.
Transformation Example:
// JSX
const element = <h1 className='greeting'>Hello, World!</h1>;
// Becomes this JavaScript:
const element = React.createElement(
'h1', // type
{ className: 'greeting' }, // props
'Hello, World!' // children
);
React.createElement Signature:
React.createElement(type, props, ...children);
- type: String (for HTML elements) or Component (for React components)
- props: Object containing element attributes and properties
- children: Any number of child elements or text content
What createElement Returns:
It returns a plain JavaScript object called a "React element":
{
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, World!'
},
key: null,
ref: null,
$$typeof: Symbol(react.element)
}
3. Virtual DOM
The Virtual DOM is React's secret weapon for performance. It's a lightweight, in-memory representation of the actual DOM, stored as a tree of JavaScript objects.
What is the Virtual DOM?
- A programming concept where a "virtual" representation of the DOM is kept in memory
- A tree of React elements (plain JavaScript objects)
- Much faster to manipulate than the real DOM
- Enables React to calculate the most efficient way to update the UI
Virtual DOM Structure:
// Virtual DOM representation of <div><h1>Title</h1><p>Content</p></div>
{
type: 'div',
props: {
children: [
{
type: 'h1',
props: { children: 'Title' }
},
{
type: 'p',
props: { children: 'Content' }
}
]
}
}
Benefits of Virtual DOM:
- Performance: Batches DOM updates and minimizes expensive DOM operations
- Predictability: Makes UI updates more predictable and easier to debug
- Declarative: Allows you to describe what the UI should look like, not how to change it
4. Diffing Algorithm
The diffing algorithm is React's process for determining what changes need to be made to the real DOM. It compares the new Virtual DOM tree with the previous Virtual DOM tree.
How Diffing Works:
1. Tree Comparison
React compares trees level by level, starting from the root:
- If root elements have different types, React tears down the old tree and builds a new one
- If root elements have the same type, React compares their attributes
2. Component Comparison
When comparing components:
- Same component type: React updates the props and calls lifecycle methods
- Different component type: React unmounts the old component and mounts the new one
3. Child Element Comparison
React uses keys to efficiently handle lists of children:
// Without keys (inefficient)
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
// With keys (efficient)
<ul>
<li key="1">Item 1</li>
<li key="2">Item 2</li>
</ul>
Diffing Optimizations:
- Heuristic Algorithm: React uses heuristics that work well for most use cases
- Key Prop: Uses keys to identify which items have changed, added, or removed
- Component Boundaries: Limits diffing scope to component boundaries
Reconciliation Process:
- Element Type Check: Compare element types first
- Props Comparison: If types match, compare props
- Children Recursion: Recursively apply the same process to children
- Update Queue: Build a list of necessary DOM operations
5. Real DOM Updates
After diffing determines what needs to change, React updates the actual DOM with minimal operations.
DOM Update Process:
1. Commit Phase
React applies all the changes calculated during the diffing phase:
- Deletion: Remove elements that no longer exist
- Addition: Add new elements
- Update: Modify existing elements' attributes or content
2. Batching Updates
React batches multiple state updates together for performance:
// These will be batched together
setState({ count: count + 1 });
setState({ name: 'New Name' });
setState({ active: true });
3. Effect Execution
After DOM updates, React runs effects (useEffect, componentDidMount, etc.)
Performance Benefits:
- Minimal DOM Manipulation: Only updates what actually changed
- Batched Updates: Groups multiple changes into single DOM operations
- Asynchronous Updates: Updates happen asynchronously when possible
React Fiber Architecture (React 16)
Modern React uses Fiber architecture to make this process even more efficient:
What is Fiber?
- A complete rewrite of React's core algorithm
- Enables incremental rendering
- Allows React to pause, abort, or restart rendering work
- Prioritizes updates based on their importance
Key Fiber Features:
- Time Slicing: Breaks work into small chunks
- Prioritization: High-priority updates (user input) interrupt low-priority ones
- Suspense: Enables components to "wait" for something before rendering
- Concurrent Features: Enables concurrent rendering capabilities
Best Practices for Performance
Understanding React's internals helps you write more performant code:
1. Use Keys Properly
// Good: Stable, unique keys
{
items.map((item) => <Item key={item.id} data={item} />);
}
// Bad: Array indices as keys
{
items.map((item, index) => <Item key={index} data={item} />);
}
2. Minimize Re-renders
// Use React.memo for functional components
const ExpensiveComponent = React.memo(({ data }) => {
return <div>{/* Complex rendering */}</div>;
});
// Use useMemo for expensive calculations
const expensiveValue = useMemo(() => {
return computeExpensiveValue(props.data);
}, [props.data]);
3. Keep Components Small
- Smaller components make diffing more efficient
- Easier to optimize individual components
- Better separation of concerns
4. Avoid Inline Objects and Functions
// Bad: Creates new object on every render
<Component style={{ marginTop: 10 }} />;
// Good: Define outside render or use useMemo
const styles = { marginTop: 10 };
<Component style={styles} />;
Conclusion
React's under-the-hood workflow is a sophisticated system designed for performance and developer experience:
- JSX provides a declarative way to describe UI
- React.createElement transforms JSX into JavaScript objects
- Virtual DOM creates an efficient in-memory representation
- Diffing determines the minimal set of changes needed
- Real DOM updates apply only the necessary changes
Understanding this process helps you:
- Write more performant React applications
- Debug issues more effectively
- Make informed architectural decisions
- Optimize your components for React's reconciliation process
The beauty of React is that while this complex process happens under the hood, you can focus on writing declarative, component-based UI code while React handles the efficient updates for you.