If you ask a React Native developer: "Does React Native use a Virtual DOM?", the answer is usually a hesitant "Yes... sort of."
Technically, the "DOM" (Document Object Model) is a Web concept. There are no <div> or <span> tags in your phone. However, the mechanism—the core brain that makes React Native performant—is exactly the same. We call it the Virtual Tree (or React Element Tree).
In this article, we will explore Reconciliation: The algorithm that decides when to repaint a wall and when to tear down the entire house.
1. The Expensive Truth: Native Views
Why do we need a "Virtual" tree in the first place?
Imagine you are building a house (User Interface):
Virtual Tree: This is a blueprint on paper. You can erase a wall, redraw a window, or crumble the paper up in milliseconds. It is cheap.
Native Views (
UIViewon iOS,android.view.Viewon Android): These are the actual bricks and mortar. Creating a new native view requires memory allocation, GPU rendering, and layout calculation. It is expensive.
If React Native destroyed and recreated the actual Native Views every time your state changed, your app would run at 1 FPS and burn the user's battery instantly.
Reconciliation is the strategic process of comparing the new Blueprint with the old Blueprint to minimize the work required by the Builders (Native UI Thread).
2. The Diffing Algorithm: O(n) Heuristics
In computer science, comparing two trees to find the minimum number of operations is typically an O(n³) complexity problem. For an app with 1,000 elements, that would mean one billion calculations. Too slow.
React uses a "heuristic" (smart guessing) algorithm to bring this down to O(n). To achieve this, it relies on two major assumptions that every Good Developer must know:
Rule #1: Different Types = Total Destruction
If the root element type changes, React tears down the old tree and builds a new one from scratch.
Old:
<View><Text>Hi</Text></View>New:
<ScrollView><Text>Hi</Text></ScrollView>
Even though the inside is the same, React sees that View changed to ScrollView. Action:
Unmount the old View (Native View destroyed, state lost).
Mount the new ScrollView (New Native View created).
Pitfall: Never define a component inside another component.
const Parent = () => {
// ❌ BAD: This creates a new 'Child' type on every render
const Child = () => <View />;
return <Child />;
}
This forces React to hit Rule #1 continuously, causing flickering and focus loss.
Rule #2: Same Type = Update Props Only
If the element type remains the same, React keeps the underlying Native View instance.
Old:
<View style={{ width: 100 }} />New:
<View style={{ width: 200 }} />
Action: React sees the type is still View. It calculates the difference (diff) in props and sends a specific command to the Native side: "Update property 'width' to 200". The heavy Native View is preserved.
3. The List Problem: Why key is Critical
This is the most common performance bottleneck in React Native lists (FlatList, ScrollView).
Imagine a list: A -> B. You want to insert C at the top: C -> A -> B.
Without Keys:
React compares index 0: A vs C. Different? Mutate A to C.
React compares index 1: B vs A. Different? Mutate B to A.
React appends B at the end. Result: Every single row is re-rendered.
**With Keys (key="a", key="b"): React looks at the unique ID, not the index.
It sees A and B still exist.
It realizes C is the only new item. Result: It re-uses the native views for A and B, and only creates C.
4. The Native Twist: The Shadow Tree
This is where React Native differs from the Web.
On the Web, React talks to the Browser DOM. In React Native, after Reconciliation is done in the JavaScript Thread, the result is not HTML. It is a set of serialized commands (UIManager operations).
JS Thread: Calculates the diff (Reconciliation).
The Bridge (Old Arch) / JSI (New Arch): Transports these changes.
Shadow Tree (C++): A dedicated tree structure used to calculate Layout (Yoga Engine).
UI Thread: The actual pixels are drawn on the screen.
In the New Architecture (Fabric), the Reconciliation step can create the Shadow Tree directly in C++, reducing the overhead significantly and allowing for synchronous updates.
5. Performance Takeaways
Understanding Reconciliation changes how you write code:
Stability is King: Keep your component structure stable. Avoid dynamic component types.
React.memois your shield: If a parent re-renders, the child re-renders by default (even if props didn't change). Usememoto tell Reconciliation: "Stop here, nothing changed."Keys Matter: Use unique IDs (from your database) for list keys. Never use the array index
(key={index})if the list can be reordered or filtered.
Final Thoughts
Reconciliation is the "Architect" that bridges the gap between the flexibility of JavaScript and the performance of Native UI. It allows us to write declarative code ("I want a blue button") without worrying about the imperative manual labor ("Create view, set background, set frame...").
Mastering this concept is the first step in moving from "My app works" to "My app flies."
Top comments (1)
Loved the house/blueprint analogy—it really clarifies reconciliation and the Shadow Tree. Has anyone run into real-world bugs from redefining components or using bad keys in lists? Any tips on detecting these early?