NPM version
Downloads
Repo Dependents
Github repo

适合初学者的综合 React Router 6.x 备忘清单

入门

安装使用

1
2
3
4
5
6
7
8
9
$ npm create vite@latest myApp --\
--template react
# 按照提示操作
$ cd <myApp>
$ npm install react-router-dom \
localforage \
match-sorter \
sort-by
$ npm run dev

添加路由器

{3-6,8-13,19}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React from "react";
import ReactDOM from "react-dom/client";
import {
createBrowserRouter,
RouterProvider,
} from "react-router-dom";

const router = createBrowserRouter([
{
path: "/",
element: <div>Hello world!</div>,
},
]);

const root=document.getElementById('root');

ReactDOM.createRoot(root).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);

根路由

{1,6}
1
2
3
4
5
6
7
8
import Root from "./routes/root";

const router = createBrowserRouter([
{
path: "/",
element: <Root />,
},
]);

处理未找到错误

{1,7}
1
2
3
4
5
6
7
8
9
import ErrorPage from "./error-page";

const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <ErrorPage />,
},
]);

contacts 用户界面

{1,9-12}
1
2
3
4
5
6
7
8
9
10
11
12
13
import Contact from "./routes/contact";

const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <ErrorPage />,
},
{
path: "contacts/:contactId",
element: <Contact />,
},
]);

嵌套路由

src/main.jsx

{6-11}
1
2
3
4
5
6
7
8
9
10
11
12
13
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <ErrorPage />,
children: [
{
path: "contacts/:contactId",
element: <Contact />,
},
],
},
]);

src/routes/root.jsx

{1,8}
1
2
3
4
5
6
7
8
9
10
11
12
import { Outlet } from "react-router-dom";

export default function Root() {
return (
<>
{/* 所有其他元素 */}
<div id="detail">
<Outlet />
</div>
</>
);
}

客户端路由

{2,9,14}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import {
Outlet, Link
} from "react-router-dom";

export default function Root() {
return (
<ul>
<li>
<Link to={`contacts/1`}>
Your Name
</Link>
</li>
<li>
<Link to={`contacts/2`}>
Your Friend
</Link>
</li>
</ul>
);
}

创建联系人

创建动作并将 <form> 更改为 <Form>

{5,8,11-14,20-22}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import {
Outlet,
Link,
useLoaderData,
Form,
} from "react-router-dom";
import {
getContacts, createContact
} from "../contacts";

export async function action() {
const contact = await createContact();
return { contact };
}
export default function Root() {
const { contacts } = useLoaderData();
return (
<div id="sidebar">
<h1>React Router Contacts</h1>
<Form method="post">
<button type="submit">New</button>
</Form>
</div>
);
}

导入并设置路由上的 action

{3,12}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Root, {
loader as rootLoader,
action as rootAction,
} from "./routes/root";

const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <ErrorPage />,
loader: rootLoader,
action: rootAction,
children: [
{
path: "contacts/:contactId",
element: <Contact />,
},
],
},
]);

加载程序中的 URL 参数

{3}
1
2
3
4
5
6
[
{
path: "contacts/:contactId",
element: <Contact />,
},
];

:contactId URL 段。 冒号(:)具有特殊含义,将其变成“动态段

{1-4,6-8,11}
1
2
3
4
5
6
7
8
9
10
11
12
13
import {
useLoaderData
} from "react-router-dom";
import { getContact } from "../contacts";

export async function loader({ params }) {
return getContact(params.contactId);
}

export default function Contact() {
const contact = useLoaderData();
// existing code
}

在路由上配置 loader

{2,16}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Contact, {
loader as contactLoader,
} from "./routes/contact";

const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <ErrorPage />,
loader: rootLoader,
action: rootAction,
children: [
{
path: "contacts/:contactId",
element: <Contact />,
loader: contactLoader,
},
],
},
]);

更新数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import {
Form, useLoaderData
} from "react-router-dom";

export default function EditContact() {
const contact = useLoaderData();

return (
<Form method="post" id="contact-form">
<label>
<span>Twitter</span>
<input
type="text"
name="twitter"
placeholder="@jack"
defaultValue={contact.twitter}
/>
</label>
<label>
<span>Notes</span>
<textarea
name="notes"
defaultValue={contact.notes}
rows={6}
/>
</label>
<p>
<button type="submit">保存</button>
<button type="button">取消</button>
</p>
</Form>
);
}

添加新的编辑路由

{1,16-20}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import EditContact from "./routes/edit";

const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <ErrorPage />,
loader: rootLoader,
action: rootAction,
children: [
{
path: "contacts/:contactId",
element: <Contact />,
loader: contactLoader,
},
{
path: "contacts/:contactId/edit",
element: <EditContact />,
loader: contactLoader,
},
],
},
]);

活动链接样式

{2,7-8}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import {
NavLink,
} from "react-router-dom";

<NavLink
to={`contacts/${contact.id}`}
className={({ isActive, isPending }) =>
isActive
? "active"
: isPending
? "pending"
: ""
}
>
{/* other code */}
</NavLink>

全局待定用户界面

{7,13-14}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import {
useNavigation,
} from "react-router-dom";

export default function Root() {
const { contacts } = useLoaderData();
const navigation = useNavigation();

return (
<div
id="detail"
className={
navigation.state === 'loading'
? 'loading' : ''
}
>
<Outlet />
</div>
);
}

使用 FormData 更新联系人

向编辑模块添加一个动作

{4,6,8-13}
1
2
3
4
5
6
7
8
9
10
11
12
13
import {
Form,
useLoaderData,
redirect,
} from "react-router-dom";
import { updateContact } from "../contacts";

export async function action({ request, params }) {
const formData = await request.formData();
const updates = Object.fromEntries(formData);
await updateContact(params.contactId, updates);
return redirect(`/contacts/${params.contactId}`);
}

将动作连接到路由

{2,22}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import EditContact, {
action as editAction,
} from "./routes/edit";

const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <ErrorPage />,
loader: rootLoader,
action: rootAction,
children: [
{
path: "contacts/:contactId",
element: <Contact />,
loader: contactLoader,
},
{
path: "contacts/:contactId/edit",
element: <EditContact />,
loader: contactLoader,
action: editAction,
},
],
},
]);

删除记录

{3}
1
2
3
4
5
6
7
8
9
10
11
<Form
method="post"
action="destroy"
onSubmit={(event) => {
if (!confirm("请确认您要删除此记录")) {
event.preventDefault();
}
}}
>
<button type="submit">删除</button>
</Form>

添加销毁动作

1
2
3
4
5
6
7
import {redirect} from "react-router-dom";
import {deleteContact} from "../contacts";

export async function action({ params }) {
await deleteContact(params.contactId);
return redirect("/");
}

destroy 路由添加到路由配置中

{2,9-12}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import {
action as destroyAction
} from "./routes/destroy";

const router = createBrowserRouter([
{
path: "/",
children: [
{
path: "contacts/:contactId/destroy",
action: destroyAction,
},
],
},
]);

上下文错误

{2}
1
2
3
4
5
export async function action({ params }) {
throw new Error("oh dang!");
await deleteContact(params.contactId);
return redirect("/");
}

让我们为 destroy 路由创建上下文错误消息:

{5}
1
2
3
4
5
6
7
[
{
path: "contacts/:contactId/destroy",
action: destroyAction,
errorElement: <div>哎呀!有一个错误</div>,
},
];

首页路由

{1,11}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import Index from "./routes/index";

const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <ErrorPage />,
loader: rootLoader,
action: rootAction,
children: [
{ index: true, element: <Index /> },
],
},
]);

取消按钮

{5,10,18-20}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import {
Form,
useLoaderData,
redirect,
useNavigate,
} from "react-router-dom";

export default function Edit() {
const contact = useLoaderData();
const navigate = useNavigate();

return (
<Form method="post" id="contact-form">
<div>
<button type="submit">保存</button>
<button
type="button"
onClick={() => {
navigate(-1);
}}
>
取消
</button>
</div>
</Form>
);
}

使用客户端路由获取提交

<form> 更改为 <Form>

{1,9}
1
2
3
4
5
6
7
8
9
<Form id="search-form" role="search">
<input
id="q"
aria-label="Search contacts"
placeholder="Search"
type="search"
name="q"
/>
</Form>

如果有 URLSearchParams 过滤列表

{1,4}
1
2
3
4
5
6
export async function loader({ request }) {
const url = new URL(request.url);
const q = url.searchParams.get("q");
const contacts = await getContacts(q);
return { contacts };
}

将 URL 同步到表单状态

{5,9,20}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
export async function loader({ request }) {
const url = new URL(request.url);
const q = url.searchParams.get("q");
const contacts = await getContacts(q);
return { contacts, q };
}

export default function Root() {
const { contacts, q } = useLoaderData();
const navigation = useNavigation();

return (
<Form id="search-form" role="search">
<input
id="q"
aria-label="Search contacts"
placeholder="Search"
type="search"
name="q"
defaultValue={q}
/>
{/* existing code */}
</Form>
);
}

将输入值与 URL 搜索参数同步

{1,7-9}
1
2
3
4
5
6
7
8
9
10
import { useEffect } from "react";

export default function Root() {
const { contacts, q } = useLoaderData();
const navigation = useNavigation();

useEffect(() => {
document.getElementById("q").value = q;
}, [q]);
}

提交变更 Forms

{2,8,19-21}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import {
useSubmit,
} from "react-router-dom";

export default function Root() {
const { contacts, q } = useLoaderData();
const navigation = useNavigation();
const submit = useSubmit();

return (
<Form id="search-form" role="search">
<input
id="q"
aria-label="Search contacts"
placeholder="Search"
type="search"
name="q"
defaultValue={q}
onChange={(event) => {
submit(event.currentTarget.form);
}}
/>
{/* existing code */}
</Form>
);
}

Routers

挑选路由器

在 v6.4 中,引入了支持新数据 API 的新路由器:

:–
createBrowserRouter #
createMemoryRouter #
createHashRouter #

以下路由器不支持数据 data API:

:–
<BrowserRouter> #
<MemoryRouter> #
<HashRouter> #
<NativeRouter> #
<StaticRouter> #

路由示例

快速更新到 v6.4 的最简单方法是从 createRoutesFromElements 获得帮助,因此您无需将 <Route> 元素转换为路由对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import {
createBrowserRouter,
createRoutesFromElements,
Route,
RouterProvider,
} from "react-router-dom";

const router = createBrowserRouter(
createRoutesFromElements(
<Route path="/" element={<Root />}>
<Route path="dashboard" element={<Dashboard />} />
{/* ... etc. */}
</Route>
)
);

ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);

createBrowserRouter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import * as React from "react";
import * as ReactDOM from "react-dom";
import {
createBrowserRouter,
RouterProvider,
} from "react-router-dom";

import Root, {rootLoader} from "./root";
import Team, {teamLoader} from "./team";

const router = createBrowserRouter([
{
path: "/",
element: <Root />,
loader: rootLoader,
children: [
{
path: "team",
element: <Team />,
loader: teamLoader,
},
],
},
]);

const root=document.getElementById('root');

ReactDOM.createRoot(root).render(
<RouterProvider router={router} />
);

Type Declaration

1
2
3
4
5
6
7
function createBrowserRouter(
routes: RouteObject[],
opts?: {
basename?: string;
window?: Window;
}
): RemixRouter;

routes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
createBrowserRouter([
{
path: "/",
element: <Root />,
loader: rootLoader,
children: [
{
path: "events/:id",
element: <Event />,
loader: eventLoader,
},
],
},
]);

basename 用于您无法部署到域的根目录,而是部署到子目录的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
createBrowserRouter(routes, {
basename: "/app",
});

createBrowserRouter(routes, {
basename: "/app",
});
<Link to="/" />;
// results in <a href="/app" />

createBrowserRouter(routes, {
basename: "/app/",
});
<Link to="/" />;
// results in <a href="/app/" />

createHashRouter

{4,11}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import * as React from "react";
import * as ReactDOM from "react-dom";
import {
createHashRouter,
RouterProvider,
} from "react-router-dom";

import Root, { rootLoader } from "./root";
import Team, { teamLoader } from "./team";

const router = createHashRouter([
{
path: "/",
element: <Root />,
loader: rootLoader,
children: [
{
path: "team",
element: <Team />,
loader: teamLoader,
},
],
},
]);

const root=document.getElementById('root');

ReactDOM.createRoot(root).render(
<RouterProvider router={router} />
);

createMemoryRouter

{2-3,24-27}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import {
RouterProvider,
createMemoryRouter,
} from "react-router-dom";
import * as React from "react";
import {
render,
waitFor,
screen,
} from "@testing-library/react";
import "@testing-library/jest-dom";
import CalendarEvent from "./event";

test("event route", async () => {
const FAKE_EVENT = { name: "测试事件" };
const routes = [
{
path: "/events/:id",
element: <CalendarEvent />,
loader: () => FAKE_EVENT,
},
];

const router=createMemoryRouter(routes,{
initialEntries: ["/", "/events/123"],
initialIndex: 1,
});

render(
<RouterProvider router={router} />
);

await waitFor(
() => screen.getByRole("heading")
);
expect(screen.getByRole("heading"))
.toHaveTextContent(
FAKE_EVENT.name
);
});

initialEntries

1
2
3
createMemoryRouter(routes, {
initialEntries: ["/", "/events/123"],
});

initialIndex

{3}
1
2
3
4
5
createMemoryRouter(routes, {
initialEntries: ["/", "/events/123"],
initialIndex: 1,
// start at "/events/123"
});

<RouterProvider>

{26}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import {
createBrowserRouter,
RouterProvider,
} from "react-router-dom";

const router = createBrowserRouter([
{
path: "/",
element: <Root />,
children: [
{
path: "dashboard",
element: <Dashboard />,
},
{
path: "about",
element: <About />,
},
],
},
]);

const root=document.getElementById('root');

ReactDOM.createRoot(root).render(
<RouterProvider
router={router}
fallbackElement={<BigSpinner />}
/>
);

fallbackElement 如果您不是服务器渲染您的应用程序,DataBrowserRouter 将在安装时启动所有匹配的路由加载器。 在此期间,您可以提供一个 fallbackElement 来向用户表明该应用程序正在运行

{3}
1
2
3
4
<RouterProvider
router={router}
fallbackElement={<SpinnerOfDoom />}
/>

Router Components

<BrowserRouter>

{4}
1
2
3
4
5
6
7
8
9
10
11
12
import * as React from "react";
import * as ReactDOM from "react-dom";
import {
BrowserRouter
} from "react-router-dom";

const root=document.getElementById('root');
ReactDOM.createRoot(root).render(
<HashRouter>
{/* 你的应用程序的其余部分在这里 */}
</HashRouter>,
);

<BrowserRouter> 使用干净的 URL 将当前位置存储在浏览器的地址栏中,并使用浏览器的内置历史堆栈进行导航

<HashRouter>

{4}
1
2
3
4
5
6
7
8
9
10
11
12
import * as React from "react";
import * as ReactDOM from "react-dom";
import {
HashRouter
} from "react-router-dom";

const root=document.getElementById('root');
ReactDOM.createRoot(root).render(
<HashRouter>
{/* 你的应用程序的其余部分在这里 */}
</HashRouter>,
);

<HashRouter> 用于 Web 浏览器,因为某些原因不应(或不能)将 URL 发送到服务器

<NativeRouter>

{3}
1
2
3
4
5
6
7
8
9
10
11
12
import * as React from "react";
import {
NativeRouter
} from "react-router-native";

function App() {
return (
<NativeRouter>
{/* 你的应用程序的其余部分在这里 */}
</NativeRouter>
);
}

<NativeRouter> 是在 React Native 应用程序中运行 React Router 的推荐接口

<MemoryRouter>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import * as React from "react";
import { create } from "react-test-renderer";
import {
MemoryRouter,
Routes,
Route,
} from "react-router-dom";

describe("My app", () => {
it("renders correctly", () => {
let renderer = create(
<MemoryRouter initialEntries={["/users/mjackson"]}>
<Routes>
<Route path="users" element={<Users />}>
<Route path=":id" element={<UserProfile />} />
</Route>
</Routes>
</MemoryRouter>
);

expect(renderer.toJSON()).toMatchSnapshot();
});
});

<MemoryRouter> 在内部将其位置存储在一个数组中。 与 <BrowserHistory><HashHistory> 不同,它不依赖于外部源,例如浏览器中的历史堆栈。 这使得它非常适合需要完全控制历史堆栈的场景,例如测试

<Router>

<Router> 是所有路由器组件(如 <BrowserRouter><StaticRouter>)共享的低级接口。 就 React 而言,<Router> 是一个上下文提供者,它向应用程序的其余部分提供路由信息

<StaticRouter>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import * as React from "react";
import * as ReactDOMServer from "react-dom/server";
import { StaticRouter } from "react-router-dom/server";
import http from "http";

function requestHandler(req, res) {
let html = ReactDOMServer.renderToString(
<StaticRouter location={req.url}>
{/* 你的应用程序的其余部分在这里 */}
</StaticRouter>
);

res.write(html);
res.end();
}

http.createServer(requestHandler)
.listen(3000);

另见


评论
avatar
竹山一叶
技术分享 个人心得
Follow Me
公告
欢迎光临小站,这里是我日常工作和学习中收集和整理的总结,希望能对你有所帮助:)

本站的内容经过个人加工总结而来,也参考了网友们分享的资料,如有侵权,请第一时间联系我,我将及时进行修改或删除😊
最新文章
网站资讯
文章数目 :
437
已运行时间 :
本站总字数 :
431.6k
本站访客数 :
本站总访问量 :
最后更新时间 :
文章归档文章分类文章标签复制本文标题复制本文地址
随便逛逛