Tổng quan
Để tối ưu được ứng dụng React, bạn cần hiểu rõ cơ chế hoạt động bên trong của React. Bài viết này sẽ giải thích chi tiết về Virtual DOM, Reconciliation Algorithm và cách React quyết định khi nào cần re-render.
1. Virtual DOM là gì?

Giải thích chi tiết hình minh họa:
Cột 1 - Real DOM (DOM thật trong Browser):
- DOM thật là cấu trúc cây HTML thực tế mà browser hiển thị
- Mỗi lần thay đổi DOM, browser phải re-render (tính toán lại layout, paint, composite)
- Thao tác trực tiếp với DOM rất chậm và tốn kém về hiệu năng
- Ví dụ:
document.getElementById('app').innerHTML = '...'gây re-render toàn bộ subtree
Cột 2 - Virtual DOM (Đại diện của DOM bằng JavaScript):
- Virtual DOM (VDOM) là một JavaScript object đại diện cho cấu trúc DOM
- React giữ 2 bản VDOM: Previous VDOM (trạng thái cũ) và New VDOM (trạng thái mới sau khi state/props thay đổi)
- VDOM rất nhẹ, chỉ là object JavaScript nên thao tác cực nhanh
- VNode có cấu trúc:
js:
{ type: 'div', props: { className: 'container' }, children: [ { type: 'h1', props: {}, children: 'Hello' }, { type: 'p', props: {}, children: 'World' } ] }
Cột 3 - Diffing & Reconciliation (So sánh và Điều hòa):
- React so sánh Previous VDOM với New VDOM để tìm sự khác biệt
- Thuật toán Diffing chỉ so sánh các node cùng cấp (same level)
- Khi phát hiện thay đổi, React tạo ra Patch (bản vá) - danh sách thay đổi tối thiểu
- Cuối cùng, React áp dụng batch updates vào Real DOM một lần duy nhất
- Ví dụ trong hình:
- Patch 1: Cập nhật text của
<p>từ "World" → "React" - Patch 2: Thêm
<li>thứ 4 vào danh sách
- Patch 1: Cập nhật text của
Tại sao Virtual DOM nhanh hơn?
// ❌ BAD: Thao tác trực tiếp DOM (chậm)
document.getElementById('list').innerHTML = '';
items.forEach(item => {
const li = document.createElement('li');
li.textContent = item;
document.getElementById('list').appendChild(li); // Re-render mỗi lần
});
// ✅ GOOD: React Virtual DOM (nhanh)
function List({ items }) {
return (
<ul>
{items.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
);
}
// React tự động batch tất cả thay đổi và chỉ update DOM 1 lần2. React Hooks Lifecycle

Giải thích chi tiết 3 giai đoạn:
MOUNTING PHASE (Giai đoạn khởi tạo)
Khi component lần đầu tiên được render:
- Component Rendered: React gọi function component lần đầu tiên
- useState/useReducer (Initialize State): Tạo state ban đầu
js:
const [count, setCount] = useState(0); // count = 0 lần đầu - DOM Update: React commit VDOM thay đổi vào Real DOM
- useEffect (Empty Dependency Array []): Chạy sau khi DOM đã update
js:
useEffect(() => { console.log('Component mounted - chỉ chạy 1 lần'); // Dùng để: fetch data, subscribe events, setup timers }, []); // [] = chỉ chạy khi mount - Effects Run (After Paint): Browser đã vẽ xong UI, effects mới chạy
UPDATING PHASE (Giai đoạn cập nhật)
Khi props hoặc state thay đổi:
- Props or State Change: Trigger re-render
js:
setCount(count + 1); // State thay đổi - Re-render: React gọi lại function component
- DOM Update: So sánh VDOM và cập nhật Real DOM
- useEffect Cleanup (Previous Effect): Dọn dẹp effect cũ trước
js:
useEffect(() => { const timer = setInterval(() => console.log('tick'), 1000); return () => clearInterval(timer); // Cleanup function }, [count]); - useEffect (With Dependencies [deps]): Chạy effect mới nếu deps thay đổi
- Effects Run (After Paint): Sau khi browser vẽ UI
UNMOUNTING PHASE (Giai đoạn hủy)
Khi component bị remove khỏi DOM:
- Component Unmounting: React chuẩn bị xóa component
- useEffect Cleanup (Return Function): Dọn dẹp resources
js:
useEffect(() => { const subscription = api.subscribe(); return () => { subscription.unsubscribe(); // Cleanup khi unmount }; }, []); - Component Removed from DOM: React xóa component khỏi Real DOM
3. Reconciliation Algorithm (Thuật toán điều hòa)
React sử dụng thuật toán Heuristic O(n) thay vì O(n³) truyền thống.
Quy tắc cốt lõi:
Quy tắc 1: So sánh theo type
// Case 1: Type khác nhau → Hủy và tạo mới
// Old VDOM
<div><Counter /></div>
// New VDOM
<span><Counter /></span>
// React làm gì?
// 1. Unmount toàn bộ <div> và <Counter> cũ
// 2. Mount lại <span> và <Counter> mới (state Counter bị reset!)Quy tắc 2: So sánh theo key
// ❌ BAD: Không có key
{items.map(item => <Item data={item} />)}
// Khi xóa item đầu, React re-render TẤT CẢ items
// ✅ GOOD: Có key
{items.map(item => <Item key={item.id} data={item} />)}
// React biết chính xác item nào bị xóa/thêm, chỉ update đúng item đóQuy tắc 3: Children Reconciliation
// Case: Thêm item vào cuối danh sách
// Old: [<li key="1">A</li>, <li key="2">B</li>]
// New: [<li key="1">A</li>, <li key="2">B</li>, <li key="3">C</li>]
// React: Chỉ mount <li key="3">C</li>
// Case: Thêm item vào đầu (KHÔNG có key)
// Old: [<li>A</li>, <li>B</li>]
// New: [<li>C</li>, <li>A</li>, <li>B</li>]
// React: Re-render TẤT CẢ vì không biết đâu là C mới4. Optimization Techniques (Kỹ thuật tối ưu)
4.1. Tránh Unnecessary Re-renders
// ❌ BAD: Object/Array mới mỗi lần render
function Parent() {
const config = { theme: 'dark' }; // Object MỚI mỗi lần render
return <Child config={config} />; // Child bị re-render mặc dù props giống nhau
}
// ✅ GOOD: Dùng useMemo
function Parent() {
const config = useMemo(() => ({ theme: 'dark' }), []); // Chỉ tạo 1 lần
return <Child config={config} />; // Child không re-render
}4.2. React.memo cho Component
// ❌ BAD: Component con re-render mỗi khi Parent render
function ExpensiveChild({ data }) {
// Heavy computation
return <div>{data.map(...)}</div>;
}
// ✅ GOOD: Dùng React.memo
const ExpensiveChild = React.memo(function ExpensiveChild({ data }) {
return <div>{data.map(...)}</div>;
});
// Chỉ re-render khi `data` thay đổi (shallow comparison)4.3. useCallback cho Event Handlers
// ❌ BAD: Function mới mỗi lần render
function Parent() {
const handleClick = () => console.log('clicked'); // Function MỚI
return <Child onClick={handleClick} />; // Child re-render
}
// ✅ GOOD: Dùng useCallback
function Parent() {
const handleClick = useCallback(() => console.log('clicked'), []);
return <Child onClick={handleClick} />; // Child KHÔNG re-render
}4.4. Key Prop Pattern
// ❌ BAD: Dùng index làm key
{items.map((item, index) => <Item key={index} data={item} />)}
// Khi sắp xếp/xóa item, React nhầm lẫn vì index thay đổi
// ✅ GOOD: Dùng unique ID
{items.map(item => <Item key={item.id} data={item} />)}5. Performance Debugging
Chrome DevTools React Profiler
// 1. Bật Highlight Updates trong React DevTools
// 2. Quan sát component nào re-render không cần thiết
// 3. Thêm name cho anonymous components
export default React.memo(function MyComponent() {
// ...
});
// 4. Sử dụng Profiler API
import { Profiler } from 'react';
function onRenderCallback(id, phase, actualDuration) {
console.log(`${id} took ${actualDuration}ms`);
}
<Profiler id="Navigation" onRender={onRenderCallback}>
<Navigation />
</Profiler>6. Common Mistakes (Lỗi thường gặp)
Mistake 1: Mutating State
// ❌ BAD: Mutate trực tiếp
const addItem = () => {
items.push(newItem); // Mutate array
setItems(items); // React KHÔNG phát hiện thay đổi
};
// ✅ GOOD: Tạo array mới
const addItem = () => {
setItems([...items, newItem]); // Immutable update
};Mistake 2: setState trong render
// ❌ BAD: setState trong render body
function Component() {
const [count, setCount] = useState(0);
setCount(count + 1); // Infinite loop!
return <div>{count}</div>;
}
// ✅ GOOD: setState trong event handler hoặc useEffect
function Component() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1); // OK
}, []);
return <div>{count}</div>;
}Mistake 3: Missing Dependencies
// ❌ BAD: Thiếu dependencies
useEffect(() => {
fetchData(userId); // userId KHÔNG có trong deps
}, []); // Warning: React Hook useEffect has a missing dependency
// ✅ GOOD: Đầy đủ dependencies
useEffect(() => {
fetchData(userId);
}, [userId]); // Re-run khi userId thay đổi7. Best Practices Checklist
- ✅ Luôn dùng
keyprop cho lists - ✅ Tránh tạo object/array/function mới trong render
- ✅ Dùng
React.memocho component có rendering nặng - ✅ Dùng
useMemocho computed values nặng - ✅ Dùng
useCallbackcho event handlers truyền xuống child - ✅ Tách component lớn thành components nhỏ
- ✅ Đặt state gần nhất với nơi sử dụng
- ✅ Sử dụng React DevTools Profiler để tìm bottleneck
Kết luận
Hiểu rõ Virtual DOM và Reconciliation giúp bạn:
- Viết code hiệu quả hơn (tránh re-render không cần thiết)
- Debug performance issues nhanh hơn
- Thiết kế component architecture tốt hơn
- Tối ưu ứng dụng cho production
Tiếp theo, hãy đọc Performance Optimization và Server Components để hiểu sâu hơn về Next.js!