一、数据加载器(Loader)
1. 基础用法
作用:在路由组件渲染前加载数据
适用场景:页面初始化数据获取、权限验证
// 路由配置
{
path: '/user/:userId',
element: <UserProfile />,
loader: async ({ params }) => {
const response = await fetch(`/api/users/${params.userId}`);
if (!response.ok) throw new Error('用户不存在');
return response.json();
}
}
// 组件中使用
import { useLoaderData } from 'react-router-dom';
const UserProfile = () => {
const userData = useLoaderData(); // 直接获取 loader 返回的数据
return <div>{userData.name}</div>;
};
2. 进阶功能
权限验证(自动重定向):
{
path: '/dashboard',
element: <Dashboard />,
loader: () => {
if (!localStorage.getItem('token')) {
return redirect('/login'); // 自动跳转登录页
}
return null;
}
}
并行加载(多个路由的 loader 同时执行):
const router = createBrowserRouter([
{
path: '/',
element: <Layout />,
loader: layoutLoader, // 第一个 loader
children: [
{
index: true,
element: <Home />,
loader: homeLoader // 第二个并行执行的 loader
}
]
}
]);
二、数据操作(Action)
1. 基础表单提交
作用:处理表单 POST/PUT/DELETE 请求
// 路由配置
{
path: '/posts/create',
element: <CreatePost />,
action: async ({ request }) => {
const formData = await request.formData();
const newPost = {
title: formData.get('title'),
content: formData.get('content')
};
const response = await fetch('/api/posts', {
method: 'POST',
body: JSON.stringify(newPost)
});
if (!response.ok) return { error: '创建失败' };
return redirect('/posts'); // 成功后跳转
}
}
// 组件中使用
import { Form, useActionData } from 'react-router-dom';
const CreatePost = () => {
const actionData = useActionData(); // 获取 action 返回的数据
return (
<Form method="post">
<input name="title" />
<textarea name="content" />
<button type="submit">提交</button>
{actionData?.error && <div className="error">{actionData.error}</div>}
</Form>
);
};
2. 文件上传处理
action: async ({ request }) => {
const formData = await request.formData();
const file = formData.get('avatar');
const uploadResponse = await fetch('/api/upload', {
method: 'POST',
body: file
});
return uploadResponse.json();
}
三、错误处理
1. 全局错误边界
// 路由配置
{
path: '/',
element: <Root />,
errorElement: <ErrorPage />, // 错误显示组件
children: [
{
path: 'product/:id',
element: <Product />,
loader: productLoader // 这里抛出的错误会被 errorElement 捕获
}
]
}
// ErrorPage.jsx
import { useRouteError } from 'react-router-dom';
const ErrorPage = () => {
const error = useRouteError();
return (
<div>
<h1>出错啦!</h1>
<p>{error.message}</p>
</div>
);
};
2. 局部错误处理
{
path: '/settings',
element: <Settings />,
loader: async () => {
try {
const data = await fetchSecureData();
return data;
} catch (error) {
throw new Response('权限不足', { status: 403 }); // 自定义错误响应
}
},
errorElement: <AuthError /> // 专门处理权限错误的组件
}
四、最佳实践
-
分离数据逻辑
将 loader/action 逻辑单独放在routes目录:src/ routes/ post/ loader.js action.js -
类型安全(TypeScript):
interface LoaderData { post: Post; comments: Comment[]; } export const postLoader = async ({ params }) => { // ...加载逻辑 return data as LoaderData; } const PostPage = () => { const data = useLoaderData() as LoaderData; // ... } -
缓存优化:
// 使用 stale-while-revalidate 策略 loader: async () => { const cached = sessionStorage.getItem('cachedData'); if (cached) return JSON.parse(cached); const freshData = await fetchData(); sessionStorage.setItem('cachedData', JSON.stringify(freshData)); return freshData; } -
加载状态指示:
import { useNavigation } from 'react-router-dom'; const SubmitButton = () => { const navigation = useNavigation(); return ( <button disabled={navigation.state === 'submitting'}> {navigation.state === 'submitting' ? '提交中...' : '提交'} </button> ); };
五、注意事项
-
不要滥用 loader
简单状态管理应使用useState或 Context,只有路由级别的数据需求才使用 loader -
避免副作用
loader 应该只用于数据获取,不要在其中修改全局状态 -
生产环境错误处理
建议使用 Sentry 等工具捕获 loader/action 中的错误 -
浏览器兼容性
Data API 需要支持现代浏览器(IE 不支持)
通过结合这些功能,您可以构建出:
✅ 自动处理加载状态的应用
✅ 具备统一错误处理的系统
✅ 支持乐观更新的交互界面
✅ 具备路由级权限控制的解决方案
建议通过官方示例库实践练习:
github.com/remix-run/r…