useReducer là một hook trong React dùng để quản lý state phức tạp, đặc biệt khi logic cập nhập state phụ thuộc vào nhiều hành động (actions) hoặc liên quan đến nhiều giá trị con trong state.
Hiểu cách đơn giản, useReducer giống như useState nhưng thay vì set state trực tiếp, bạn gửi (dispatch) một action mô tả công việc, và một hàm reducer sẽ quyết định cách cập nhập state.
Ngoài ra useReducer rất giống với State machine ở nguyên tắc hoạt động, chỉ khác ở mức độ “quy củ” và “ràng buộc”, nên ta có thể xem useReducer là một state machine đơn giản, nơi mà:
statelà dữ liệuactionlà sự kiệnreducerlà bảng quy tắc chuyển trạng thái
Ví dụ
Trong ví dụ này mình sẽ sử dụng useState để implement form login, sau đó refactor với useReducer để chúng ta có cái nhìn khách quan về 2 phương pháp
import { useState } from "react";
function LoginForm() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
function handleSubmit(e) {
e.preventDefault();
if (!email || !password) {
setError("Please fill all fields");
} else {
setError("");
console.log("Logging in with:", { email, password });
}
}
return (
<form onSubmit={handleSubmit}>
<input
type="email"
placeholder="Email"
value={email}
onChange={e => setEmail(e.target.value)}
/>
<input
type="password"
placeholder="Password"
value={password}
onChange={e => setPassword(e.target.value)}
/>
{error && <p>{error}</p>}
<button type="submit">Login</button>
</form>
);
}Với việc sử dụng useState ở trên, điểm mạnh là dễ hiểu, nhưng khi form có số lượng field nhiều hơn, chúng ta sẽ cần dùng nhiều state, và việc cập nhập sẽ lộn xộn, khó quản lý.
import { useReducer } from "react";
const initialState = {
email: "",
password: "",
error: "",
};
function reducer(state, action) {
switch (action.type) {
case "setField":
return { ...state, [action.field]: action.value };
case "setError":
return { ...state, error: action.error };
case "reset":
return initialState;
default:
return state;
}
}
function LoginForm() {
const [state, dispatch] = useReducer(reducer, initialState);
function handleSubmit(e) {
e.preventDefault();
if (!state.email || !state.password) {
dispatch({ type: "setError", error: "Please fill all fields" });
} else {
dispatch({ type: "setError", error: "" });
console.log("Logging in with:", state);
dispatch({ type: "reset" });
}
}
return (
<form onSubmit={handleSubmit}>
<input
type="email"
placeholder="Email"
value={state.email}
onChange={e =>
dispatch({ type: "setField", field: "email", value: e.target.value })
}
/>
<input
type="password"
placeholder="Password"
value={state.password}
onChange={e =>
dispatch({
type: "setField",
field: "password",
value: e.target.value,
})
}
/>
{state.error && <p>{state.error}</p>}
<button type="submit">Login</button>
</form>
);
}Với việc sử dụng useReducer như trên, ta thấy các state của các field input được gom chung vào một state, thay vì nằm rải rác, dễ quản lý, tất cả các logic của state được quản lý trong reducer, giúp tổ chức logic rõ ràng hơn về mặt kỹ thuật, ổn định và dễ test hơn, tuy nhiên nó cũng khiến source code khó đọc hơn, nhất là với các component nhỏ, người đọc phải thường nhảy lên reducer mới hiểu chuyện gì xảy ra.