前端错误处理方案
这篇笔记整理了在 Vine 项目中落地的前端错误处理方案,从中抽象出可以在其他工程中复用的实践思路。
按错误类型拆分处理
整体思路是:按错误来源拆分责任,请求错误和渲染错误分别在各自最合适的层级集中处理。
| 错误类型 | 处理方案 | 实现位置 |
|---|---|---|
| 请求错误 | TanStack Query 全局回调 | QueryCache.onError / MutationCache.onError |
| 渲染错误 | React Router errorElement | 路由配置 errorElement |
请求错误
通过 QueryCache 和 MutationCache 的 onError 回调统一处理所有 HTTP 请求错误,在一个地方集中做「HTTP 状态码 → UI 行为」的映射。
处理策略
| 错误类型 | 处理方式 |
|---|---|
| 401 Unauthorized(未认证 - 不知道你是谁) | 清除登录态,跳转登录页 |
| 403 Forbidden(未授权 - 知道你是谁,但你没权限) | Toast 提示无权限 |
| 404 Not Found | Toast 提示资源不存在 |
| 5xx Server Error | Toast 提示服务器错误 |
| 网络错误 | Toast 提示网络异常 |
| 业务错误 | Toast 显示后端返回的 message |
Router 内创建 QueryClient
请求错误处理(如 401 自动跳转登录页)需要使用 navigate。在 Vine 的实现中,直接把 QueryClientProvider 放到 Router 树内部创建,避免「路由桥接」这类全局可变注入带来的时序窗口问题。
tsx
// src/core/router/RootLayout.tsx
export function RootLayout() {
const navigate = useNavigate()
const [queryClient] = useState(() => createQueryClient(navigate))
return (
<QueryClientProvider client={queryClient}>
<VAppLayout />
</QueryClientProvider>
)
}跳过全局处理
某些组件需要自行处理错误时,可以通过 meta 字段通知全局 handler:
tsx
const { data, error, refetch } = useQuery({
queryKey: ['orders'],
queryFn: fetchOrders,
meta: { skipGlobalErrorHandler: true }
})
if (error) {
return <CustomErrorUI error={error} onRetry={refetch} />
}渲染错误
通过 React Router 的 errorElement 捕获路由组件内的渲染错误(例如 TypeError)。这类错误通常是开发阶段遗漏的边界情况,应该在开发期就尽量消灭,但仍然需要有兜底。
配置方式
tsx
// src/core/router/routes.tsx
export const routes: RouteObject[] = [
{
path: '/',
element: <RootLayout />,
errorElement: <RouteErrorFallback />,
children: [...]
}
];典型触发场景
- 组件渲染时抛出异常(如
TypeError: xxx.map is not a function) - 数据处理异常(空值、类型不符等)
- 第三方库渲染错误
项目结构示例
src/core/
├── http/
│ ├── axios/
│ │ ├── instance.ts # Axios 实例
│ │ └── interceptors/
│ │ ├── request.ts # 请求拦截(添加 token)
│ │ └── response.ts # 响应拦截(解包数据)
│ └── query/
│ ├── queryClient.ts # createQueryClient + 全局错误回调
│ └── errorHandlers.ts # 请求错误处理函数
└── router/
├── routes.tsx # 路由配置(含 errorElement)
├── RootLayout.tsx # Router 内创建 QueryClientProvider
└── RouteErrorFallback.tsx # 渲染错误 UI防御性编码
虽然 RouteErrorFallback 能捕获渲染错误,但触发它意味着页面已经崩溃、用户看到的是兜底 UI。因此在业务代码中仍然需要做好防御性编码。
tsx
// ❌ 危险:data.list 如果是非可迭代值会抛出 TypeError
const items = [...(data?.list || [])]
// ✅ 安全:确保是数组再使用
const items = Array.isArray(data?.list) ? data.list : []核心原则是:凡是来源不受你完全控制的数据(接口返回、URL 参数、本地存储等),在使用前都要做类型和空值检查。