← Về danh sách bài họcBài 11/25
🎛️ Bài 11: useReducer - Quản Lý State Phức Tạp
🎯 Sau bài học này, bạn sẽ:
- Hiểu reducer pattern và dispatch actions
- So sánh useReducer vs useState
- Xây dựng shopping cart với useReducer
- Kết hợp useReducer + useContext (mini Redux)
1. useReducer Là Gì?
useReducer quản lý state phức tạp theo pattern reducer: state + action → new state. Lấy cảm hứng từ Redux.
import { useReducer } from 'react';
// Reducer function: (state, action) => newState
function counterReducer(state, action) {
switch (action.type) {
case 'INCREMENT': return { count: state.count + 1 };
case 'DECREMENT': return { count: state.count - 1 };
case 'RESET': return { count: 0 };
case 'SET': return { count: action.payload };
default: return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+1</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>-1</button>
<button onClick={() => dispatch({ type: 'RESET' })}>Reset</button>
<button onClick={() => dispatch({ type: 'SET', payload: 100 })}>Set 100</button>
</div>
);
}2. Shopping Cart Thực Tế
const initialCart = { items: [], total: 0 };
function cartReducer(state, action) {
switch (action.type) {
case 'ADD_ITEM': {
const exists = state.items.find(i => i.id === action.payload.id);
if (exists) {
return {
...state,
items: state.items.map(i =>
i.id === action.payload.id
? { ...i, qty: i.qty + 1 }
: i
),
total: state.total + action.payload.price
};
}
return {
items: [...state.items, { ...action.payload, qty: 1 }],
total: state.total + action.payload.price
};
}
case 'REMOVE_ITEM':
const item = state.items.find(i => i.id === action.payload);
return {
items: state.items.filter(i => i.id !== action.payload),
total: state.total - (item.price * item.qty)
};
case 'CLEAR':
return initialCart;
default:
return state;
}
}
function ShoppingCart() {
const [cart, dispatch] = useReducer(cartReducer, initialCart);
const addItem = (product) =>
dispatch({ type: 'ADD_ITEM', payload: product });
return (
<div>
<h2>🛒 Giỏ hàng ({cart.items.length})</h2>
{cart.items.map(item => (
<div key={item.id}>
{item.name} x{item.qty} - {item.price * item.qty}đ
<button onClick={() =>
dispatch({ type: 'REMOVE_ITEM', payload: item.id })
}>Xóa</button>
</div>
))}
<p><strong>Tổng: {cart.total}đ</strong></p>
</div>
);
}3. useReducer vs useState
| Tiêu chí | useState | useReducer |
|---|---|---|
| Khi nào | State đơn giản | State phức tạp, nhiều actions |
| Cập nhật | setState(value) | dispatch({ type, payload }) |
| Logic | Inline trong component | Tách riêng reducer function |
| Test | Khó test riêng | Dễ test reducer (pure function) |
📝 Tóm Tắt
useReducer(reducer, initialState)→[state, dispatch]- Reducer = pure function:
(state, action) => newState - Tốt cho: state phức tạp, nhiều actions, cần test riêng logic
- Kết hợp
useReducer + useContext= mini Redux