- 1 Giới thiệu về React Hooks
- 2 useState – Quản lý state đơn giản
- 3 useEffect – Quản lý side-effect trong component
- 4 useRef – Lưu trữ tham chiếu và DOM manipulation
- 5 useMemo và useCallback – Tối ưu hiệu năng
- 6 useReducer – Quản lý state phức tạp
- 7 Custom Hooks – Tái sử dụng logic
- 8 Các lưu ý, best practices khi dùng Hooks
- 9 Tổng kết
Giới thiệu về React Hooks
Hooks là gì? Vì sao được giới thiệu từ React 16.8
React Hooks là một cơ chế mới được giới thiệu từ phiên bản React 16.8, cho phép bạn sử dụng state và các tính năng của React mà không cần phải viết class component. Trước đây, nếu bạn muốn quản lý state hoặc xử lý vòng đời component, bạn buộc phải dùng class, dẫn đến code phức tạp và khó tái sử dụng logic.
Với Hooks, bạn có thể viết component hoàn toàn bằng function – nhẹ hơn, dễ test hơn và dễ chia sẻ logic hơn.
So sánh với class component lifecycle
Hooks thay thế các lifecycle methods bằng cách trừu tượng hóa thành các hàm như useEffect, cho phép bạn kiểm soát khi nào một logic chạy sau render hoặc khi unmount. Ví dụ:
| Class Component | Hooks Equivalent |
|---|---|
componentDidMount | useEffect(() => {}, []) |
componentDidUpdate | useEffect(() => {}, [dep]) |
componentWillUnmount | useEffect(() => { return () => {} }, []) |
Hooks giúp gom nhóm logic liên quan với nhau thay vì bị tách rời theo từng lifecycle method như trước.
Lợi ích của Hooks trong phát triển React hiện đại
- Tăng khả năng tái sử dụng logic qua custom hooks
- Code ngắn gọn, dễ đọc, không cần dùng
this - Dễ test và kiểm soát side effect
- Hỗ trợ tốt cho tư duy functional programming trong UI
useState – Quản lý state đơn giản
Cách sử dụng cơ bản
useState là Hook cơ bản nhất dùng để quản lý state trong functional component:
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Bạn đã bấm {count} lần</p>
<button onClick={() => setCount(count + 1)}>Bấm tôi</button>
</div>
);
}
Ở đây, useState(0) trả về một mảng gồm count và setCount. Mỗi lần gọi setCount, component sẽ render lại với giá trị mới.
Cập nhật state bất đồng bộ và batch update
Điều quan trọng cần nhớ là việc cập nhật state là bất đồng bộ. Nếu bạn gọi nhiều lần setState liên tiếp, React có thể batch lại để tối ưu render.
setCount(count + 1);
setCount(count + 1); // Không tăng 2 lần như bạn nghĩ!
Để xử lý đúng, nên dùng function:
setCount(prev => prev + 1);
setCount(prev => prev + 1); // Giờ sẽ tăng đúng 2 lần
Lưu ý khi làm việc với object hoặc array state
Khi state là object hoặc array, cần clone trước khi update:
const [user, setUser] = useState({ name: '', age: 0 });
setUser(prev => ({ ...prev, age: 25 }));
Nếu bạn mutate trực tiếp (ví dụ: user.age = 25), React sẽ không nhận biết thay đổi và không render lại.
useEffect – Quản lý side-effect trong component
Cách hoạt động và thời điểm chạy useEffect
useEffect dùng để xử lý các side effect – ví dụ: gọi API, đăng ký sự kiện, thao tác DOM. Nó chạy sau khi render, và có thể tùy biến qua dependency array:
useEffect(() => {
console.log("Component mounted hoặc count thay đổi");
}, [count]);
Cleanup function và useEffect với dependency
Khi cần dọn dẹp (clean up) như bỏ đăng ký sự kiện, bạn có thể return một hàm từ useEffect:
useEffect(() => {
const timer = setInterval(() => console.log("tick"), 1000);
return () => clearInterval(timer);
}, []);
Cleanup sẽ được gọi khi component unmount hoặc dependency thay đổi.
useEffect vs useLayoutEffect: khi nào dùng cái nào?
useEffect: chạy sau khi DOM đã vẽ xong => phù hợp với thao tác async hoặc API.useLayoutEffect: chạy ngay trước khi vẽ DOM => dùng khi cần đo kích thước hoặc thao tác layout (scroll, animation).
Nếu không chắc, hãy dùng useEffect – trừ khi bạn có lý do đặc biệt cần đồng bộ hóa với DOM layout.
useRef – Lưu trữ tham chiếu và DOM manipulation
Dùng useRef để truy cập DOM
Hook useRef thường được sử dụng để truy cập trực tiếp tới DOM node trong functional component – thay thế cho createRef() trong class:
import { useRef } from 'react';
function InputFocus() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current?.focus();
};
return (
<>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus</button>
</>
);
}
useRef như một biến không gây re-render
Khác với useState, giá trị lưu trong useRef sẽ không gây re-render khi thay đổi. Do đó, nó phù hợp để lưu trữ các giá trị tạm thời, như timeout ID, previous value hoặc đếm số lần render:
const renderCount = useRef(0);
renderCount.current += 1;
So sánh useRef với useState trong một số use case
| Mục đích | Dùng Hook nào? |
|---|---|
| Lưu giá trị thay đổi không cần render | useRef |
| Gây trigger re-render khi thay đổi | useState |
| Truy cập DOM trực tiếp | useRef |
Tóm lại: Nếu bạn cần lưu giá trị xuyên suốt giữa các lần render mà không cần hiển thị lại giao diện, useRef là lựa chọn nhẹ và hiệu quả.
useMemo và useCallback – Tối ưu hiệu năng
Khi nào cần dùng useMemo / useCallback?
Trong React, mỗi lần component re-render thì tất cả function và object/array sẽ được tạo mới lại, gây ra re-render không cần thiết ở các component con nếu dùng React.memo.
useMemo: Ghi nhớ giá trị đã tính toán.useCallback: Ghi nhớ hàm callback đã tạo.
Tránh render lại không cần thiết
Ví dụ với useMemo:
const expensiveValue = useMemo(() => {
return computeExpensiveValue(data);
}, [data]);
Và với useCallback:
const handleClick = useCallback(() => {
console.log('clicked');
}, []);
Nhờ vậy, các component con dùng React.memo sẽ không bị re-render khi không cần thiết.
Ví dụ tối ưu một danh sách lớn hoặc component con
const ListItem = React.memo(({ onClick }) => {
console.log("Render ListItem");
return <button onClick={onClick}>Click me</button>;
});
function App() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log("Clicked");
}, []);
return (
<>
<ListItem onClick={handleClick} />
<button onClick={() => setCount(count + 1)}>Tăng count</button>
</>
);
}
Nếu không dùng useCallback, mỗi lần App re-render thì handleClick sẽ là hàm mới => ListItem cũng render lại.
useReducer – Quản lý state phức tạp
Khái niệm reducer pattern
useReducer là Hook cho phép bạn quản lý state phức tạp bằng cách áp dụng mô hình reducer – tương tự Redux. Thay vì cập nhật state trực tiếp, bạn dispatch action vào reducer để thay đổi state.
Khi nào nên dùng useReducer thay vì useState?
- Khi state là object phức tạp, nhiều trường
- Khi logic cập nhật state rẽ nhánh theo nhiều loại action
- Khi muốn tổ chức logic rõ ràng, dễ test và scale
Tổ chức logic reducer rõ ràng và testable
Ví dụ đơn giản:
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</>
);
}
Ưu điểm: Giúp tách logic cập nhật state ra khỏi UI, dễ bảo trì và test độc lập.
Custom Hooks – Tái sử dụng logic
Tạo custom hook đơn giản: useWindowSize, useDebounce, useFetch
Custom hook là những function bắt đầu bằng use, có thể gọi các hook khác bên trong, dùng để tái sử dụng logic có state/side-effect.
Ví dụ: custom hook để lấy kích thước cửa sổ trình duyệt:
import { useState, useEffect } from 'react';
function useWindowSize() {
const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight });
useEffect(() => {
const handleResize = () => setSize({ width: window.innerWidth, height: window.innerHeight });
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return size;
}
Hook này có thể dùng ở mọi component:
const { width, height } = useWindowSize();
Các custom hook phổ biến khác:
useDebounce(value, delay)useFetch(url)usePrevious(value)
Quy tắc đặt tên và cách tái sử dụng
- Tên phải bắt đầu bằng
use - Có thể dùng các hook khác bên trong
- Logic phải tách biệt giao diện (không render JSX trong custom hook)
Custom hook giúp giữ component “gọn gàng” và tăng khả năng tái sử dụng.
Chia sẻ logic giữa các component phức tạp
Thay vì copy-paste các đoạn useEffect, useState, bạn chỉ cần viết một lần trong custom hook. Điều này giúp DRY (Don’t Repeat Yourself) và bảo trì dễ hơn.
Ví dụ: nhiều component cần debounce giá trị đầu vào? Viết useDebounce() một lần – dùng mọi nơi.
Các lưu ý, best practices khi dùng Hooks
Quy tắc “Rules of Hooks”
React yêu cầu tuân thủ 2 quy tắc:
- Chỉ gọi Hook ở cấp độ cao nhất – không gọi trong vòng lặp, điều kiện, hoặc hàm con.
- Chỉ gọi Hook trong function component hoặc custom hook
Sai quy tắc sẽ dẫn đến lỗi runtime hoặc hành vi không xác định.
// Không được làm thế này!
if (someCondition) {
useEffect(() => {}, []);
}
Debug và kiểm tra hook trong môi trường dev
- Dùng React Developer Tools để xem giá trị state/hook theo component
- Dùng eslint-plugin-react-hooks để phát hiện vi phạm rules of hooks
- Có thể test custom hook với thư viện như @testing-library/react-hooks
Gợi ý thư viện hỗ trợ (react-use, ahooks, …)
Các thư viện cung cấp hàng trăm custom hooks sẵn dùng:
react-use: hook quản lý lifecycle, sensor, media, permission, v.v.ahooks: do Alibaba phát triển, mạnh mẽ và nhiều tính năng nâng caozustand: quản lý global state siêu nhẹ, hook-first
Sử dụng thư viện đúng cách giúp bạn tập trung vào logic business thay vì reinvent the wheel.
Tổng kết
Trong bài viết này, bạn đã đi qua hành trình từ các hook cơ bản như useState, useEffect, đến các hook nâng cao như useReducer, useMemo, useRef, và đặc biệt là custom hook để tái sử dụng logic.
Việc thành thạo React Hooks không chỉ giúp code bạn sạch hơn, gọn hơn, mà còn dễ bảo trì, mở rộng, và phù hợp với xu hướng hiện đại trong phát triển frontend.
Lộ trình tiếp theo
Nếu bạn đã quen với các hook cơ bản, hãy tìm hiểu thêm về:
React Query– quản lý server state với hook-based APIZustand,Jotai– quản lý global state không cần boilerplateSWR– hook fetch dữ liệu siêu đơn giản của Vercel
Hooks chỉ là khởi đầu – nhưng là nền móng quan trọng cho hệ sinh thái React hiện đại.








