React Hooks không phải là ma thuật. Chúng là các hàm Javascript thông thường dựa vào Closure và một danh sách liên kết (Linked List) được lưu trong React Fiber.
Hãy cùng mổ xẻ từng hook phổ biến.
1. useState: Ký ức của Component
Tại sao nó tồn tại?
Trong JS thuần, biến local let count = 0 sẽ bị reset mỗi khi hàm chạy lại. React Components là hàm, nên nó cần một "kho chứa" bên ngoài để ghi nhớ giá trị count giữa các lần render. kho chứa đó nằm trong Fiber Node.
Cơ chế hoạt động (Simplified)
// Mô phỏng useState bằng Array (Fiber Hooks List)
let hooks = []; // Kho chứa
let cursor = 0; // Con trỏ đọc/ghi
function useState(initialValue) {
const currentCursor = cursor; // Nhớ vị trí của slot này
hooks[currentCursor] = hooks[currentCursor] || initialValue; // Init nếu chưa có
const setState = (newValue) => {
hooks[currentCursor] = newValue;
render(); // Kích hoạt render lại
};
cursor++; // Tiến tới slot tiếp cho hook sau
return [hooks[currentCursor], setState];
}1.3. Tại sao thứ tự gọi Hook lại quan trọng?
Vì React lưu hooks trong một Linked List (hoặc Array trong ví dụ trên), nó dựa vào thứ tự gọi để biết useState nào thuộc về biến nào.
Lần render 1:
1. useState('A') -> Slot[0]
2. useState('B') -> Slot[1]
3. useEffect() -> Slot[2]
Lần render 2 (Nếu bạn dùng if để ẩn Hook 2):
1. useState('A') -> Slot[0] (OK)
2. useEffect() -> Slot[1] (😱 Lỗi! React tưởng đây là useState('B'))👉 Hậu quả: React sẽ lấy state của hook này lắp vào hook kia, gây lỗi "Rendered fewer hooks than expected" hoặc sai dữ liệu nghiêm trọng. Đó là lý do Rule of Hooks cấm gọi hook trong if/for.
❌ Cách dùng sai (Anti-pattern)
1. State dư thừa (Redundant State) Lưu cái có thể tính toán được vào state.
// ❌ Bad
const [firstName] = useState('John');
const [lastName] = useState('Doe');
const [fullName, setFullName] = useState('John Doe'); // Dư thừa, dễ sync sai
// ✅ Good
const fullName = `${firstName} ${lastName}`; // Tính toán ngay trong render (Derived State)2. Update state dựa trên biến cũ mà không dùng callback
// ❌ Bad: Nếu gọi 2 lần liên tiếp, kết quả có thể sai do closure cũ
setCount(count + 1);
setCount(count + 1); // Kết quả chỉ tăng 1
// ✅ Good
setCount(prev => prev + 1);
setCount(prev => prev + 1); // Kết quả tăng 22. useEffect: Đồng bộ hóa với Bên ngoài
Tại sao nó tồn tại?
React là Pure Function (Input -> Output). Nhưng app thực tế cần: gọi API, lắng nghe WebSocket, tương tác DOM thật... Đây là Side Effects. useEffect là nơi an toàn để thực hiện chúng sau khi render xong.
Mental Model (Mô hình tư duy)
useEffect không phải là Lifecycle (componentDidMount). Hãy nghĩ nó là: "Đồng bộ hóa (Synchronize) hệ thống với một biến dependency".
⚠️ Lưu ý chết người: Dependency Array
- Danh sách
[]rỗng: Chỉ chạy 1 lần (Mount). - Không truyền
[]: Chạy mỗi lần render (Dễ infinite loop). [prop, input]: Chạy khiprophoặcinputthay đổi (chỉ so sánh nông===).
❌ Common Pitfall: Object/Array Dependency
// ❌ Bad: Infinite Loop tiềm ẩn
const options = { id: 1 }; // Object mới được tạo ra mỗi lần render (địa chỉ RAM khác nhau)
useEffect(() => {
fetchData(options);
}, [options]); // Effect chạy lại liên tục -> Loop!
// ✅ Fix: Dùng useMemo hoặc chuyển object ra ngoài component
const options = useMemo(() => ({ id: 1 }), []);3. useMemo & useCallback: Performance Tuning
Bản chất
Cả hai đều dùng kỹ thuật Memoization (Ghi nhớ kết quả).
useMemo: Ghi nhớ kết quả trả về của hàm (Giá trị).useCallback: Ghi nhớ chính cái hàm đó (Tham chiếu).
Khi nào dùng? (Không phải lúc nào cũng dùng!)
✅ Dùng khi:
- Tính toán rất nặng: Filter/Sort array 10,000 items.
useMemo. - Referential Equality: Bạn truyền một object/function làm prop xuống component con, và component con được bọc
React.memo. Nếu không dùnguseMemo/useCallback, component con sẽ re-render vô nghĩa vì prop thay đổi tham chiếu.
❌ Đừng dùng khi:
Tính toán đơn giản (a + b, filter array 10 items). Chi phí tạo bộ nhớ cache còn đắt hơn tính lại!
// ❌ Premature Optimization
const value = useMemo(() => item.price * 2, [item.price]); // Quá thừa thãi4. useRef: Chiếc hộp ma thuật (Escape Hatch)
Tác dụng kép
- Truy cập DOM thật: Focus input, đo chiều rộng div.
- Biến Mutable không gây Re-render: Lưu giá trị mà bạn muốn thay đổi âm thầm (timer ID, previous props) mà không muốn component vẽ lại.
So sánh với useState
| useState | useRef | |
|---|---|---|
| Thay đổi giá trị | setVal(newVal) | ref.current = newVal |
| Re-render? | ✅ CÓ (Trigger render) | ❌ KHÔNG (Im lặng) |
| Khi nào dùng? | Dữ liệu hiển thị lên màn hình | Timer, DOM, biến tạm logic |
❌ Sai lầm: Đọc/Ghi Ref trong Render
// ❌ Tuyệt đối cấm
function Component() {
const count = useRef(0);
count.current++; // ⚠️ Side effect trong render! Gây bug khó hiểu khi React chạy srict mode hoặc concurrent features.
return <div>{count.current}</div>; // Dữ liệu hiển thị không nên dùng Ref
}5. useReducer: Trùm cuối quản lý State
Khi nào dùng thay useState?
- State phức tạp: State là object nhiều tầng, mảng lồng nhau.
- State phụ thuộc lẫn nhau: Chỉnh
statusthì phải reseterror, xóadata. - Business Logic dày: Muốn tách biệt logic update ra khỏi UI (như bài State Machine Flow).
(Xem chi tiết bài: Complex Flows với State Machine)
Tóm tắt: "Rule of hooks"
- Chỉ gọi ở Top Level: Không gọi trong if, for, while. Vì React dựa vào thứ tự gọi để biết hook nào là hook nào.
- Chỉ gọi trong React Component/Custom Hook: Không gọi trong hàm JS thường.