Bạn đã bao giờ cảm thấy useState trở nên quá tải khi phải quản lý nhiều state liên quan đến nhau trong một component chưa? Khi logic cập nhật state ngày càng phức tạp và những dòng setSomething, setSomethingElse bắt đầu rối tung lên? Nếu câu trả lời là "Có", thì đã đến lúc bạn làm quen với một cái tên thầm lặng nhưng cực kỳ mạnh mẽ trong "kho vũ khí" của React: useReducer Hook.

Bài viết này sẽ giúp bạn không chỉ hiểu useReducer là gì, mà còn biết chính xác khi nào nên dùng nó, cách dùng ra sao và làm thế nào để kết hợp nó tạo ra những giải pháp quản lý state thông minh và hiệu quả.
📖 useReducer là gì? Một cái nhìn trực quan
Về cơ bản, useReducer là một Hook được sử dụng để quản lý state, tương tự như useState, nhưng nó được tối ưu cho các state phức tạp và logic cập nhật cầu kỳ.
Nếu useState giống như một công tắc bật/tắt đơn giản, thì useReducer giống như một "trung tâm chỉ huy" (command center). Thay vì trực tiếp ra lệnh "hãy thay đổi state thành giá trị X", bạn sẽ gửi một "mệnh lệnh" (action) đến trung tâm chỉ huy. Trung tâm này (gọi là reducer) sẽ xem xét mệnh lệnh và state hiện tại, sau đó quyết định xem state mới sẽ trông như thế nào.

Mô hình này bao gồm 4 thành phần chính:
- State: Dữ liệu mà component của bạn cần để hoạt động, giống hệt như trong
useState. - Action: Một object mô tả hành động bạn muốn thực hiện. Nó thường có một thuộc tính
type(kiểu hành động, ví dụ: 'INCREMENT') và có thể có thêmpayload(dữ liệu đi kèm). - Reducer: Một hàm thuần túy (pure function) nhận vào
statehiện tại và mộtaction, sau đó trả về mộtstatemới. Nó chính là bộ não xử lý logic.(state, action) => newState. - Dispatch: Một hàm đặc biệt mà
useReducercung cấp cho bạn. Bạn gọi hàmdispatchvà truyền vào mộtactionđể gửi "mệnh lệnh" đến reducer.
🤔 Khi nào nên chọn useReducer thay vì useState?
Đây là câu hỏi quan trọng nhất. useReducer không phải để thay thế useState. Mỗi cái có một thế mạnh riêng. Hãy rút useReducer ra khỏi "bao kiếm" khi bạn gặp các tình huống sau:
- Logic state phức tạp: Khi việc cập nhật một state đòi hỏi nhiều bước tính toán hoặc phụ thuộc vào nhiều giá trị khác nhau. Ví dụ: quản lý giỏ hàng, form đăng ký nhiều bước.
- State có nhiều giá trị phụ thuộc lẫn nhau: Khi bạn có một object state với nhiều thuộc tính, và việc cập nhật một thuộc tính này thường kéo theo sự thay đổi của các thuộc tính khác.
- Thay vì dùng nhiều
useState:const [isLoading, setIsLoading] = useState(false) const [data, setData] = useState(null) const [error, setError] = useState(null) - Bạn có thể gom chúng lại:
const initialState = { isLoading: false, data: null, error: null } const [state, dispatch] = useReducer(reducer, initialState)
- Thay vì dùng nhiều
- State tiếp theo phụ thuộc vào state trước đó: Mặc dù
useStatecó thể xử lý việc nàysetCount(prevCount => prevCount + 1), nhưng khi logic phức tạp hơn,reducersẽ giúp mã nguồn trở nên rõ ràng và dễ kiểm thử hơn rất nhiều. - Tối ưu hiệu năng (Performance Optimization): Khi bạn cần truyền logic cập nhật state xuống các component con sâu bên trong, việc truyền hàm
dispatchthường tốt hơn là truyền các callback riêng lẻ.dispatchlà một hàm ổn định và sẽ không bị tạo lại sau mỗi lần render.
🛠️ "Thực chiến" với useReducer: Từ cơ bản đến nâng cao
Hãy cùng đi qua các ví dụ thực tế để thấy sức mạnh của useReducer.
Ví dụ 1: Bộ đếm kinh điển (The Classic Counter)
Chúng ta sẽ bắt đầu với ví dụ đơn giản nhất để hiểu cú pháp và luồng hoạt động.
import React, { useReducer } from 'react'
// 1. Định nghĩa Initial State
const initialState = { count: 0 }
// 2. Viết hàm Reducer
// Nó nhận state hiện tại và action, trả về state mới.
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: 0 }
default:
throw new Error()
}
}
function Counter() {
// 3. Sử dụng useReducer trong Component
// Nó trả về state hiện tại và hàm dispatch
const [state, dispatch] = useReducer(reducer, initialState)
return (
<div>
<h2>Count: {state.count}</h2>
{/* 4. Gọi dispatch với một action để cập nhật state */}
<button onClick={() => dispatch({ type: 'increment' })}>Tăng</button>
<button onClick={() => dispatch({ type: 'decrement' })}>Giảm</button>
<button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
</div>
)
}
export default Counter
Luồng hoạt động:
- Người dùng click nút "Tăng".
- Sự kiện
onClickgọi hàmdispatch({ type: 'increment' }). - React chuyển action
{ type: 'increment' }vàstatehiện tại ({ count: 0 }) vào hàmreducer. - Bên trong
reducer,switchcase khớp với'increment'và trả về state mới là{ count: 1 }. - React nhận state mới, và re-render lại component
Countervớistate.countbây giờ là1.
Ví dụ 2: Quản lý Form phức tạp
Hãy tưởng tượng một form đăng ký. Thay vì dùng nhiều useState cho name, email, password, chúng ta có thể gom lại.
import React, { useReducer } from 'react'
const initialState = {
name: '',
email: '',
error: null,
}
function formReducer(state, action) {
switch (action.type) {
case 'SET_FIELD':
// action.payload sẽ là { field: 'name', value: 'John' }
return {
...state,
[action.payload.field]: action.payload.value,
}
case 'SET_ERROR':
return {
...state,
error: action.payload,
}
case 'RESET':
return initialState
default:
return state
}
}
function RegistrationForm() {
const [state, dispatch] = useReducer(formReducer, initialState)
const handleChange = (e) => {
dispatch({
type: 'SET_FIELD',
payload: { field: e.target.name, value: e.target.value },
})
}
const handleSubmit = (e) => {
e.preventDefault()
if (!state.name || !state.email) {
dispatch({
type: 'SET_ERROR',
payload: 'Vui lòng điền đầy đủ thông tin!',
})
} else {
console.log('Form submitted:', state)
dispatch({ type: 'RESET' })
}
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
name="name"
value={state.name}
onChange={handleChange}
placeholder="Tên"
/>
<input
type="email"
name="email"
value={state.email}
onChange={handleChange}
placeholder="Email"
/>
<button type="submit">Đăng ký</button>
{state.error && <p style={{ color: 'red' }}>{state.error}</p>}
</form>
)
}
✅ Ưu điểm ở đây là gì? Toàn bộ logic xử lý form (SET_FIELD, SET_ERROR, RESET) được gom gọn vào một nơi duy nhất là formReducer. Component RegistrationForm chỉ cần biết "gửi đi mệnh lệnh" mà không cần quan tâm chi tiết cách state được cập nhật. Điều này làm cho component sạch sẽ và dễ đọc hơn rất nhiều.
🤝 useReducer + useContext: Cặp đôi hoàn hảo để quản lý Global State
Khi ứng dụng của bạn lớn lên, bạn sẽ cần chia sẻ state giữa nhiều component khác nhau. Đây là lúc sức mạnh của useReducer thực sự tỏa sáng khi kết hợp với useContext.
Bằng cách này, bạn có thể tạo ra một "bộ chứa state" toàn cục mà không cần đến các thư viện phức tạp như Redux.
Mô hình hoạt động:
- Tạo một Context.
- Tạo một component "Provider" bao bọc toàn bộ ứng dụng (hoặc một phần của nó).
- Bên trong Provider, sử dụng
useReducerđể quản lý state. - Cung cấp cả
statevà hàmdispatchxuống cho các component con thông qua Context. - Bất kỳ component con nào cũng có thể "tiêu thụ" (consume) Context này để đọc
statehoặc gọidispatchđể cập nhật state.
Đây chính là nền tảng của rất nhiều giải pháp quản lý state "nhẹ ký" trong hệ sinh thái React.
💡 useReducer vs. Redux: Khi nào dùng cái nào?
Nếu bạn đã biết đến Redux, bạn sẽ thấy useReducer có nhiều điểm tương đồng (reducer, action, dispatch). Vậy chúng khác nhau ở đâu?
| Tiêu chí | useReducer | Redux |
|---|---|---|
| Phạm vi | Thường dùng cho state của component hoặc một nhóm component nhỏ. | Được thiết kế cho global state của toàn ứng dụng. |
| Độ phức tạp | Rất đơn giản, là một phần của React. Không cần cài đặt thêm. | Phức tạp hơn, cần cài đặt thư viện (redux, react-redux). Có nhiều khái niệm hơn (middleware, store, selectors). |
| Middleware | Không có sẵn. | Hỗ trợ mạnh mẽ (Redux Thunk, Redux Saga) để xử lý các tác vụ bất đồng bộ (side effects). |
| Boilerplate | Rất ít. | Nhiều hơn đáng kể (actions, action creators, store configuration). |
| Trường hợp sử dụng | Lý tưởng cho quản lý state phức tạp ở cấp component hoặc các ứng dụng vừa và nhỏ không muốn thêm thư viện ngoài. | Phù hợp cho các ứng dụng lớn, có nhiều state toàn cục, logic bất đồng bộ phức tạp và cần các công cụ gỡ lỗi mạnh mẽ. |
Lời khuyên: Hãy bắt đầu với useState. Khi state trở nên phức tạp, hãy nâng cấp lên useReducer. Nếu ứng dụng của bạn thực sự lớn và cần các tính năng nâng cao như middleware, lúc đó hãy cân nhắc đến Redux hoặc các giải pháp tương tự.
Kết luận: useReducer là một lựa chọn mạnh mẽ
useReducer không phải là một công cụ bạn sẽ dùng hàng ngày, nhưng nó là một "vũ khí" cực kỳ lợi hại trong những tình huống phù hợp. Nó giúp bạn tổ chức code một cách sạch sẽ, tách biệt logic ra khỏi giao diện, và làm cho việc quản lý các state phức tạp trở nên dễ thở hơn rất nhiều.
Bằng cách nắm vững useReducer, bạn đã tiến một bước dài trên con đường trở thành một nhà phát triển React chuyên nghiệp, có khả năng xây dựng những ứng dụng linh hoạt, dễ bảo trì và mở rộng. Đừng ngần ngại, hãy mở editor lên và thử sức ngay hôm nay!
![[React Basics] Hướng dẫn thiết lập môi trường phát triển React](/images/blog/how-to-set-up-a-react-development-environment.webp)
![[React Basics] useState Hook: Cách quản lý State hiệu quả trong React](/images/blog/react-usestate-hook.webp)
![[React Basics] Stateful và Stateless Components: Phân biệt và Ứng dụng trong React](/images/blog/stateful-and-stateless-components-in-react.webp)
![[React Basics] Thành thạo các Design Patterns quan trọng nhất](/images/blog/react-design-patterns.webp)