Khi các component trong ứng dụng React của bạn ngày càng phức tạp, việc quản lý state có thể trở nên khó khăn. Với nhiều logic cập nhật state nằm rải rác trong các trình xử lý sự kiện khác nhau, component của bạn có thể khó đọc và khó gỡ lỗi. Để giải quyết vấn đề này, React cung cấp một giải pháp mạnh mẽ: hợp nhất toàn bộ logic cập nhật state vào một hàm duy nhất được gọi là reducer.
Reducer là gì?
Về cốt lõi, reducer là một hàm JavaScript thuần túy (pure function). Nó nhận vào hai tham số: state hiện tại và một đối tượng action, sau đó trả về state tiếp theo.
Công thức của một reducer có thể được hình dung như sau:
(currentState, action) => newState
React sẽ lấy giá trị state mà reducer của bạn trả về để cập nhật lại giao diện.
Ba bước để chuyển từ useState sang useReducer
Bạn có thể chuyển đổi việc quản lý state từ useState sang useReducer thông qua ba bước chính.
Bước 1: Chuyển từ việc set state trực tiếp sang “dispatch” một action
Thay vì gọi các hàm setTasks hay setUser trực tiếp trong trình xử lý sự kiện, bạn sẽ gọi một hàm dispatch. Hàm này gửi đi một “action” – một đối tượng JavaScript mô tả hành động của người dùng. Việc này giúp mô tả ý định của người dùng rõ ràng hơn là mô tả cách state thay đổi.
Ví dụ, thay vì:
setTasks([…tasks, { id: nextId++, text: text, done: false }]);
Bạn sẽ dispatch một action:
dispatch({ type: ‘added’, id: nextId++, text: text });
Bước 2: Viết hàm reducer
Đây là nơi bạn tập trung toàn bộ logic cập nhật state của mình. Hàm reducer sẽ nhận vào state hiện tại và đối tượng action mà bạn đã dispatch. Dựa vào thuộc tính type của action, nó sẽ quyết định cách tạo ra state tiếp theo.
function tasksReducer(tasks, action) {
switch (action.type) {
case 'added': {
return [
...tasks,
{
id: action.id,
text: action.text,
done: false,
},
];
}
case 'deleted': {
return tasks.filter((t) => t.id !== action.id);
}
default: {
throw Error('Unknown action: ' + action.type);
}
}
}
Bước 3: Sử dụng hook useReducer trong component
Cuối cùng, bạn kết nối reducer với component của mình bằng hook useReducer. Hook này tương tự như useState nhưng nó nhận vào hàm reducer và state ban đầu làm đối số. Nó trả về giá trị state hiện tại và hàm dispatch để bạn có thể gửi các action tới reducer.
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
Khi nào nên dùng useReducer?
Mặc dù useReducer đòi hỏi nhiều thiết lập hơn một chút so với useState, nó lại mang lại nhiều lợi ích trong các trường hợp phức tạp:
- Khả năng đọc (Readability): useReducer giúp tách biệt rõ ràng giữa logic “cái gì đã xảy ra” (trong các trình xử lý sự kiện) và “state cập nhật như thế nào” (trong reducer), giúp code dễ đọc hơn.
- Gỡ lỗi (Debugging): Khi có lỗi xảy ra, bạn có thể thêm một console.log ngay bên trong reducer để xem mọi action và sự thay đổi của state. Điều này giúp việc tìm ra nguyên nhân lỗi trở nên dễ dàng hơn rất nhiều.
- Kiểm thử (Testing): Vì reducer là một hàm thuần túy không phụ thuộc vào component, bạn có thể export và kiểm thử nó một cách độc lập.
Bạn không cần phải sử dụng reducer cho mọi thứ. Hãy bắt đầu với useState và khi một component bắt đầu có nhiều logic state phức tạp, hãy cân nhắc chuyển sang useReducer để làm cho code của bạn có cấu trúc và dễ quản lý hơn.