React Router v6 数据加载
bash npm install react-router-dom@6
创建一个使用 `createBrowserRouter` 和 `RouterProvider` 的基本路由结构:
```jsx
// main.jsx
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import HomePage from './pages/Home';
import PostPage from './pages/Post';
import RootLayout from './layouts/Root';
const router = createBrowserRouter([
{
path: '/',
element: <RootLayout />,
children: [
{ index: true, element: <HomePage /> },
{ path: 'posts/:postId', element: <PostPage /> }
]
}
]);
function App() {
return <RouterProvider router={router} />;
}
export default App;
3. 定义数据加载器(Loader)
3.1 为路由添加 loader 函数
每个路由都可以定义一个 loader 函数,该函数在路由被匹配时执行,并返回组件需要的数据。
// 在路由定义中注入 loader
{
path: 'posts/:postId',
element: <PostPage />,
loader: async ({ params }) => {
const response = await fetch(`/api/posts/${params.postId}`);
if (!response.ok) throw new Error('文章加载失败');
return response.json();
}
}
loader接收一个对象参数,包含params(路径参数)、request等- 可以执行任意异步操作(fetch、数据库查询等)
- 返回的数据会被自动传递给组件
3.2 在组件中获取数据:useLoaderData
在渲染组件内部,使用 useLoaderData 钩子直接获取 loader 返回的数据。
// pages/Post.jsx
import { useLoaderData } from 'react-router-dom';
export default function PostPage() {
const post = useLoaderData(); // 即 loader 返回的数据
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
从此你不再需要在组件中用 useState 和 useEffect 管理请求状态,数据已经同步就绪。
4. 错误处理
4.1 路由级错误边界:errorElement
当 loader 抛出错误时,React Router 会自动渲染当前路由(或父路由)配置的 errorElement,实现类似错误边界的效果。
{
path: 'posts/:postId',
element: <PostPage />,
loader: postLoader,
errorElement: <PostErrorPage />
}
4.2 获取错误信息:useRouteError
在错误元素中,通过 useRouteError 获取被抛出的错误对象。
import { useRouteError, isRouteErrorResponse } from 'react-router-dom';
function PostErrorPage() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
if (error.status === 404) {
return <p>文章未找到。</p>;
}
return <p>服务器错误 {error.status}</p>;
}
return <p>未知错误发生。</p>;
}
这样你能优雅地处理 404、500 等情况,而不会导致整个页面崩溃。
5. 延迟数据加载与 Suspense
5.1 为什么要延迟加载?
有些接口响应很快,有些却很慢。如果所有数据都等待完毕才渲染页面,慢接口会阻塞整个页面展示。React Router v6 支持使用 defer 延迟部分非关键数据的加载,并配合 React 的 <Suspense> 和 <Await> 实现渐进式渲染。
5.2 使用 defer 返回部分数据
在 loader 中,用 defer 包装返回值,可以将某些数据标记为“延迟”。
import { defer } from 'react-router-dom';
const loader = async () => {
// 关键数据:立即加载
const userPromise = fetchUser(); // 但假设它也很慢
// 非关键数据:延迟加载
const commentsPromise = fetchComments();
return defer({
user: userPromise, // 就算慢,也需要等
comments: commentsPromise // 可以用 Suspense 分别处理
});
};
或者更典型的场景:一部分数据必须等待,另一部分可以稍后展示。
const loader = async ({ params }) => {
const post = await fetchPost(params.postId); // 核心内容必须加载完毕
const relatedPosts = fetchRelatedPosts(); // 可以稍后
return defer({ post, relatedPosts });
};
5.3 在组件中使用 Await 和 Suspense
组件内用 <Await> 组件包裹延迟数据,并用 <Suspense> 提供 fallback。
import { useLoaderData, Await } from 'react-router-dom';
import { Suspense } from 'react';
export default function PostPage() {
const { post, relatedPosts } = useLoaderData();
return (
<div>
<article>{post.title} - {post.content}</article>
<Suspense fallback={<p>加载推荐文章...</p>}>
<Await resolve={relatedPosts}>
{(posts) => (
<ul>
{posts.map(p => <li key={p.id}>{p.title}</li>)}
</ul>
)}
</Await>
</Suspense>
</div>
);
}
Await的resolve属性接收一个 Promise,当 Promise 状态变为 resolved 后渲染子组件- 多个延迟数据可以搭配多个
Await,每个都可拥有独立的Suspense边界,实现细粒度的加载控制
6. 数据加载的并行与请求瀑布
React Router v6 的 loader 会自动并行执行同级路由的 loader,但父子路由的 loader 默认是并行的(除非一个依赖另一个)。不过你可以通过 params 或 request 来手动优化。
示例:同时加载用户和用户帖子
两个 loader 是并行的,无需担心瀑布流。
{
path: 'users/:userId',
loader: userLoader,
children: [
{
path: 'posts',
loader: postsLoader
}
]
}
7. 表单操作与数据提交(Action)
虽然本教程聚焦数据加载,但路由还支持 action 来处理表单提交、数据变更。它与 loader 配合,形成完整的数据流闭环。简单示例:
{
path: 'new-post',
element: <NewPostPage />,
action: async ({ request }) => {
const formData = await request.formData();
const post = Object.fromEntries(formData);
await fetch('/api/posts', { method: 'POST', body: JSON.stringify(post) });
return redirect('/');
}
}