← Về danh sách bài họcBài 11/25

🎛️ Bài 11: useReducer - Quản Lý State Phức Tạp

⏱️ Thời gian đọc: 15 phút | 📚 Độ khó: Nâng cao

🎯 Sau bài học này, bạn sẽ:

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íuseStateuseReducer
Khi nàoState đơn giảnState phức tạp, nhiều actions
Cập nhậtsetState(value)dispatch({ type, payload })
LogicInline trong componentTách riêng reducer function
TestKhó test riêngDễ test reducer (pure function)

📝 Tóm Tắt