
本文详解如何在 rtk query 中正确捕获和处理 api 错误(如 400 bad request),重点解决 `error` 和 `status` 字段为 `undefined` 的常见问题,通过 `unwrap()` + `async/await` 模式实现精准错误响应解析。
RTK Query 的 mutation hooks(如 useLoginUserMutation)返回的触发函数(如 loginUser)本质上是一个 异步操作的发起器,而非同步执行并立即返回结果。这意味着:调用 loginUser(user) 后,status、error、isError 等状态字段不会“立刻更新”,而是需等待 React Query 内部的状态同步周期(通常在下一个渲染周期)。若在调用后立即检查 isError 或 error(如原代码中 if (isError || !loginResult || error) 放在 loginUser(user) 同一函数内),此时状态尚未变更,必然得到 'uninitialized' 和 undefined —— 这正是你遇到问题的根本原因。
✅ 正确做法是:显式等待 mutation 完成,并使用 .unwrap() 提取响应或抛出结构化错误。
.unwrap() 是 RTK Query 提供的关键方法,它将内部 Promise 包装为标准 Promise:成功时返回 data,失败时抛出一个包含 status、data、error 等字段的标准化错误对象(类型为 SerializedError | FetchBaseQueryError),可直接用于条件判断与业务逻辑分支。
以下是推荐的完整实现方案:
✅ 推荐写法:async/await + unwrap() 处理登录逻辑
const [loginUser, { data: loginResult, status, error, isError, isLoading }] =
useLoginUserMutation();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const user: NewUser = {
email: formData.email,
password: formData.password,
};
try {
// ✅ 关键:await 并 unwrap,确保获取真实响应或结构化错误
const data = await loginUser(user).unwrap();
console.log("Login successful:", data);
alert("Login successful");
storeUserNames();
// ✅ 成功后可跳转路由、保存 token 等
// navigate("/dashboard");
} catch (err) {
// ✅ err 是标准化错误对象,可安全访问属性
console.error("Login failed:", err);
// 类型守卫:区分网络错误 vs 业务错误(如 400)
if ("status" in err && typeof err.status === "number") {
switch (err.status) {
case 400:
alert("❌ 用户不存在或凭据错误,请检查邮箱和密码");
break;
case 401:
alert("❌ 认证失败,请重新登录");
break;
case 500:
alert("❌ 服务器内部错误,请稍后重试");
break;
default:
alert(`❌ 请求失败:${err.status} ${err.data?.message || "未知错误"}`);
}
} else {
alert("❌ 网络连接异常,请检查网络");
}
}
};⚠️ 注意事项与最佳实践
- 不要在触发 mutation 后立即读取 isError/error:这些字段属于「查询生命周期状态」,适用于 UI 渲染(如 loading spinner、错误提示框),不适用于即时逻辑分支。
- 始终使用 .unwrap() 配合 try/catch:这是获取 HTTP 状态码(如 400)、响应体(如 { message: "cannot find user" })的唯一可靠方式。
-
修正你的 baseQuery 配置:当前 baseUrl: "http://localhost:3333/login" + url: "" 会导致请求地址为 http://localhost:3333/login/(末尾斜杠),而 JSON Server 通常期望 POST /login。建议改为:
baseQuery: fetchBaseQuery({ baseUrl: "http://localhost:3333/" }), // ... query: (newUser: NewUser) => ({ url: "login", // ✅ 显式指定 endpoint method: "POST", body: newUser, }) - 避免重复 alert 和 console.error:生产环境应使用 Toast 组件或状态管理统一展示错误,而非阻塞式 alert。
? 总结
RTK Query 的错误处理核心在于理解其异步状态模型:
? isLoading / isError / error 是用于UI 响应式渲染的只读状态;
? mutation().unwrap() 是用于业务逻辑错误处理的主动式入口。
当你需要根据 HTTP 状态码(如 400 判断用户是否存在)做差异化处理时,必须使用 await mutation(...).unwrap() + catch,而非依赖初始 hook 返回的状态字段。这一模式不仅解决 undefined error 问题,更让错误处理具备可测试性、可维护性与类型安全性。










