React Hooks — Từ useState Đến Custom Hooks
Làm chủ React Hooks: useState, useEffect, useContext, useReducer và cách viết custom hooks tái sử dụng.
Hooks ra đời năm 2018 thay đổi cách viết React hoàn toàn. Không còn class, không còn this. Function components + hooks là chuẩn mực mới.
useState — State Cơ Bản#
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>+</button>
</div>
);
}tsxLuôn dùng functional update (setCount(c => c + 1)) khi state mới phụ thuộc state cũ.
useEffect — Side Effects#
useEffect(() => {
// Chạy sau khi render
fetch('/api/data').then(setData);
// Cleanup — chạy khi component unmount hoặc dependency thay đổi
return () => {
abortController.abort();
};
}, []); // [] = chỉ chạy 1 lần (mount)tsxDependency Array#
| dependencies | Khi chạy |
|---|---|
undefined | Mỗi lần render |
[] | Mount + unmount |
[a, b] | Khi a hoặc b thay đổi |
useContext — Không Còn Prop Drilling#
const ThemeContext = createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
const theme = useContext(ThemeContext);
return <div className={theme}>Hello</div>;
}tsxuseReducer — State Phức Tạp#
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment': return { count: state.count + 1 };
case 'decrement': return { count: state.count - 1 };
case 'reset': return { count: action.payload };
default: return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
<p>{state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'reset', payload: 0 })}>Reset</button>
</>
);
}tsxDùng useReducer khi state có nhiều trường phụ thuộc nhau hoặc logic phức tạp.
useRef — Giữ Giá Trị Qua Các Render#
function AutoFocus() {
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
inputRef.current?.focus();
}, []);
return <input ref={inputRef} />;
}tsxuseRef không gây re-render khi thay đổi — dùng để lưu timeout ID, DOM reference, hoặc giá trị cần giữa các render.
useMemo & useCallback — Performance#
// useMemo — memoize giá trị
const sortedList = useMemo(() => {
return items.sort((a, b) => a.name.localeCompare(b.name));
}, [items]);
// useCallback — memoize function
const handleClick = useCallback((id: number) => {
setSelected(id);
}, []);tsxKhông lạm dụng — chỉ dùng khi thực sự cần (danh sách lớn, truyền callback cho child memoized).
Custom Hooks — Tái Sử Dụng Logic#
function useWindowSize() {
const [size, setSize] = useState({ width: 0, height: 0 });
useEffect(() => {
const handleResize = () => {
setSize({ width: window.innerWidth, height: window.innerHeight });
};
window.addEventListener('resize', handleResize);
handleResize();
return () => window.removeEventListener('resize', handleResize);
}, []);
return size;
}
// Dùng
function App() {
const { width, height } = useWindowSize();
return <p>{width} x {height}</p>;
}tsxuseFetch#
function useFetch<T>(url: string) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const abort = new AbortController();
setLoading(true);
fetch(url, { signal: abort.signal })
.then(res => res.json())
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
return () => abort.abort();
}, [url]);
return { data, loading, error };
}tsxRules of Hooks#
- Chỉ gọi hooks ở top level — không trong if, loop, function lồng
- Chỉ gọi hooks trong function component hoặc custom hook
- Tên custom hook bắt đầu bằng
use
Kết Luận#
Hooks làm React đơn giản hơn, dễ đọc hơn, dễ test hơn. Nắm vững 5 hooks cơ bản (useState, useEffect, useContext, useRef, useMemo) là bạn đã xử lý được 90% tình huống. Custom hooks là cách bạn viết code React sạch và tái sử dụng.