+$ npm install react-router-dom \
+ localforage \
+ match-sorter \
+ sort-by
+$ npm run dev
+```
+
+### 添加路由器
+
+
+```jsx {3-6,8-13,19}
+import React from "react";
+import ReactDOM from "react-dom/client";
+import {
+ createBrowserRouter,
+ RouterProvider,
+} from "react-router-dom";
+
+const router = createBrowserRouter([
+ {
+ path: "/",
+ element: Hello world!
,
+ },
+]);
+
+const root=document.getElementById('root');
+
+ReactDOM.createRoot(root).render(
+
+
+
+);
+```
+
+### 根路由
+
+```jsx {1,6}
+import Root from "./routes/root";
+
+const router = createBrowserRouter([
+ {
+ path: "/",
+ element: ,
+ },
+]);
+```
+
+### 处理未找到错误
+
+```jsx {1,7}
+import ErrorPage from "./error-page";
+
+const router = createBrowserRouter([
+ {
+ path: "/",
+ element: ,
+ errorElement: ,
+ },
+]);
+```
+
+### `contacts` 用户界面
+
+```jsx {1,9-12}
+import Contact from "./routes/contact";
+
+const router = createBrowserRouter([
+ {
+ path: "/",
+ element: ,
+ errorElement: ,
+ },
+ {
+ path: "contacts/:contactId",
+ element: ,
+ },
+]);
+```
+
+### 嵌套路由
+
+`src/main.jsx`
+
+```jsx {6-11}
+const router = createBrowserRouter([
+ {
+ path: "/",
+ element: ,
+ errorElement: ,
+ children: [
+ {
+ path: "contacts/:contactId",
+ element: ,
+ },
+ ],
+ },
+]);
+```
+
+`src/routes/root.jsx`
+
+```jsx {1,8}
+import { Outlet } from "react-router-dom";
+
+export default function Root() {
+ return (
+ <>
+ {/* 所有其他元素 */}
+
+
+
+ >
+ );
+}
+```
+
+### 客户端路由
+
+```jsx {2,9,14}
+import {
+ Outlet, Link
+} from "react-router-dom";
+
+export default function Root() {
+ return (
+
+ -
+
+ Your Name
+
+
+ -
+
+ Your Friend
+
+
+
+ );
+}
+```
+
+### 创建联系人
+
+
+创建动作并将 `
+ );
+}
+```
+
+添加新的编辑路由
+
+```jsx {1,16-20}
+import EditContact from "./routes/edit";
+
+const router = createBrowserRouter([
+ {
+ path: "/",
+ element: ,
+ errorElement: ,
+ loader: rootLoader,
+ action: rootAction,
+ children: [
+ {
+ path: "contacts/:contactId",
+ element: ,
+ loader: contactLoader,
+ },
+ {
+ path: "contacts/:contactId/edit",
+ element: ,
+ loader: contactLoader,
+ },
+ ],
+ },
+]);
+```
+
+### 活动链接样式
+
+```jsx {2,7-8}
+import {
+ NavLink,
+} from "react-router-dom";
+
+
+ isActive
+ ? "active"
+ : isPending
+ ? "pending"
+ : ""
+ }
+>
+ {/* other code */}
+
+```
+
+### 全局待定用户界面
+
+```jsx {7,13-14}
+import {
+ useNavigation,
+} from "react-router-dom";
+
+export default function Root() {
+ const { contacts } = useLoaderData();
+ const navigation = useNavigation();
+
+ return (
+
+
+
+ );
+}
+```
+
+### 使用 FormData 更新联系人
+
+
+向编辑模块添加一个动作
+
+```jsx {4,6,8-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}`);
+}
+```
+
+将动作连接到路由
+
+```jsx {2,22}
+import EditContact, {
+ action as editAction,
+} from "./routes/edit";
+
+const router = createBrowserRouter([
+ {
+ path: "/",
+ element: ,
+ errorElement: ,
+ loader: rootLoader,
+ action: rootAction,
+ children: [
+ {
+ path: "contacts/:contactId",
+ element: ,
+ loader: contactLoader,
+ },
+ {
+ path: "contacts/:contactId/edit",
+ element: ,
+ loader: contactLoader,
+ action: editAction,
+ },
+ ],
+ },
+]);
+```
+
+### 删除记录
+
+```jsx {3}
+
+```
+
+添加销毁动作
+
+```jsx
+import {redirect} from "react-router-dom";
+import {deleteContact} from "../contacts";
+
+export async function action({ params }) {
+ await deleteContact(params.contactId);
+ return redirect("/");
+}
+```
+
+将 `destroy` 路由添加到路由配置中
+
+```jsx {2,9-12}
+import {
+ action as destroyAction
+} from "./routes/destroy";
+
+const router = createBrowserRouter([
+ {
+ path: "/",
+ children: [
+ {
+ path: "contacts/:contactId/destroy",
+ action: destroyAction,
+ },
+ ],
+ },
+]);
+```
+
+### 上下文错误
+
+```jsx {2}
+export async function action({ params }) {
+ throw new Error("oh dang!");
+ await deleteContact(params.contactId);
+ return redirect("/");
+}
+```
+
+让我们为 `destroy` 路由创建上下文错误消息:
+
+```jsx {5}
+[
+ {
+ path: "contacts/:contactId/destroy",
+ action: destroyAction,
+ errorElement: 哎呀!有一个错误
,
+ },
+];
+```
+
+### 首页路由
+
+```jsx {1,11}
+import Index from "./routes/index";
+
+const router = createBrowserRouter([
+ {
+ path: "/",
+ element: ,
+ errorElement: ,
+ loader: rootLoader,
+ action: rootAction,
+ children: [
+ { index: true, element: },
+ ],
+ },
+]);
+```
+
+### 取消按钮
+
+```jsx {5,10,18-20}
+import {
+ Form,
+ useLoaderData,
+ redirect,
+ useNavigate,
+} from "react-router-dom";
+
+export default function Edit() {
+ const contact = useLoaderData();
+ const navigate = useNavigate();
+
+ return (
+
+ );
+}
+```
+
+### 使用客户端路由获取提交
+
+
+将 `
+```
+
+如果有 `URLSearchParams` 过滤列表
+
+```jsx {1,4}
+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 同步到表单状态
+
+
+```jsx {5,9,20}
+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 (
+
+ );
+}
+```
+
+### 将输入值与 URL 搜索参数同步
+
+```jsx {1,7-9}
+import { useEffect } from "react";
+
+export default function Root() {
+ const { contacts, q } = useLoaderData();
+ const navigation = useNavigation();
+
+ useEffect(() => {
+ document.getElementById("q").value = q;
+ }, [q]);
+}
+```
+
+### 提交变更 Forms
+
+```jsx {2,8,19-21}
+import {
+ useSubmit,
+} from "react-router-dom";
+
+export default function Root() {
+ const { contacts, q } = useLoaderData();
+ const navigation = useNavigation();
+ const submit = useSubmit();
+
+ return (
+
+ );
+}
+```
+
+Routers
+---
+
+### 挑选路由器
+
+在 v6.4 中,引入了支持新数据 API 的新路由器:
+
+:-- | --
+:-- | --
+`createBrowserRouter` | [#](https://reactrouter.com/en/main/routers/create-browser-router)
+`createMemoryRouter` | [#](https://reactrouter.com/en/main/routers/create-memory-router)
+`createHashRouter` | [#](https://reactrouter.com/en/main/routers/create-hash-router)
+
+以下路由器不支持数据 `data` API:
+
+:-- | --
+:-- | --
+`` | [#](https://reactrouter.com/en/main/router-components/browser-router)
+`` | [#](https://reactrouter.com/en/main/router-components/memory-router)
+`` | [#](https://reactrouter.com/en/main/router-components/hash-router)
+`` | [#](https://reactrouter.com/en/main/router-components/native-router)
+`` | [#](https://reactrouter.com/en/main/router-components/static-router)
+
+### 路由示例
+
+
+快速更新到 v6.4 的最简单方法是从 createRoutesFromElements 获得帮助,因此您无需将 `` 元素转换为路由对象
+
+```jsx
+import {
+ createBrowserRouter,
+ createRoutesFromElements,
+ Route,
+ RouterProvider,
+} from "react-router-dom";
+
+const router = createBrowserRouter(
+ createRoutesFromElements(
+ }>
+ } />
+ {/* ... etc. */}
+
+ )
+);
+
+ReactDOM.createRoot(document.getElementById("root")).render(
+
+
+
+);
+```
+
+### createBrowserRouter
+
+
+```jsx
+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: ,
+ loader: rootLoader,
+ children: [
+ {
+ path: "team",
+ element: ,
+ loader: teamLoader,
+ },
+ ],
+ },
+]);
+
+const root=document.getElementById('root');
+
+ReactDOM.createRoot(root).render(
+
+);
+```
+
+Type Declaration
+
+```jsx
+function createBrowserRouter(
+ routes: RouteObject[],
+ opts?: {
+ basename?: string;
+ window?: Window;
+ }
+): RemixRouter;
+```
+
+routes
+
+```jsx
+createBrowserRouter([
+ {
+ path: "/",
+ element: ,
+ loader: rootLoader,
+ children: [
+ {
+ path: "events/:id",
+ element: ,
+ loader: eventLoader,
+ },
+ ],
+ },
+]);
+```
+
+`basename` 用于您无法部署到域的根目录,而是部署到子目录的情况
+
+```jsx
+createBrowserRouter(routes, {
+ basename: "/app",
+});
+
+createBrowserRouter(routes, {
+ basename: "/app",
+});
+;
+// results in
+
+createBrowserRouter(routes, {
+ basename: "/app/",
+});
+;
+// results in
+```
+
+### createHashRouter
+
+```jsx {4,11}
+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: ,
+ loader: rootLoader,
+ children: [
+ {
+ path: "team",
+ element: ,
+ loader: teamLoader,
+ },
+ ],
+ },
+]);
+
+const root=document.getElementById('root');
+
+ReactDOM.createRoot(root).render(
+
+);
+```
+
+### createMemoryRouter
+
+
+```jsx {2-3,24-27}
+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: ,
+ loader: () => FAKE_EVENT,
+ },
+ ];
+
+ const router=createMemoryRouter(routes,{
+ initialEntries: ["/", "/events/123"],
+ initialIndex: 1,
+ });
+
+ render(
+
+ );
+
+ await waitFor(
+ () => screen.getByRole("heading")
+ );
+ expect(screen.getByRole("heading"))
+ .toHaveTextContent(
+ FAKE_EVENT.name
+ );
+});
+```
+
+initialEntries
+
+```jsx
+createMemoryRouter(routes, {
+ initialEntries: ["/", "/events/123"],
+});
+```
+
+initialIndex
+
+```jsx {3}
+createMemoryRouter(routes, {
+ initialEntries: ["/", "/events/123"],
+ initialIndex: 1,
+ // start at "/events/123"
+});
+```
+
+### \
+
+```jsx {26}
+import {
+ createBrowserRouter,
+ RouterProvider,
+} from "react-router-dom";
+
+const router = createBrowserRouter([
+ {
+ path: "/",
+ element: ,
+ children: [
+ {
+ path: "dashboard",
+ element: ,
+ },
+ {
+ path: "about",
+ element: ,
+ },
+ ],
+ },
+]);
+
+const root=document.getElementById('root');
+
+ReactDOM.createRoot(root).render(
+ }
+ />
+);
+```
+
+`fallbackElement` 如果您不是服务器渲染您的应用程序,DataBrowserRouter 将在安装时启动所有匹配的路由加载器。 在此期间,您可以提供一个 fallbackElement 来向用户表明该应用程序正在运行
+
+```jsx {3}
+}
+/>
+```
+
+Router Components
+---
+
+### \
+
+```jsx {4}
+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(
+
+ {/* 你的应用程序的其余部分在这里 */}
+ ,
+);
+```
+
+`` 使用干净的 `URL` 将当前位置存储在浏览器的地址栏中,并使用浏览器的内置历史堆栈进行导航
+
+### \
+
+```jsx {4}
+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(
+
+ {/* 你的应用程序的其余部分在这里 */}
+ ,
+);
+```
+
+`` 用于 Web 浏览器,因为某些原因不应(或不能)将 URL 发送到服务器
+
+### \
+
+```jsx {3}
+import * as React from "react";
+import {
+ NativeRouter
+} from "react-router-native";
+
+function App() {
+ return (
+
+ {/* 你的应用程序的其余部分在这里 */}
+
+ );
+}
+```
+
+`` 是在 React Native 应用程序中运行 React Router 的推荐接口
+
+### \
+
+
+```jsx
+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(
+
+
+ }>
+ } />
+
+
+
+ );
+
+ expect(renderer.toJSON()).toMatchSnapshot();
+ });
+});
+```
+
+`` 在内部将其位置存储在一个数组中。 与 `` 和 `` 不同,它不依赖于外部源,例如浏览器中的历史堆栈。 这使得它非常适合需要完全控制历史堆栈的场景,例如测试
+
+### \
+
+`` 是所有路由器组件(如 `` 和 ``)共享的低级接口。 就 React 而言,`` 是一个上下文提供者,它向应用程序的其余部分提供路由信息
+
+### \
+
+```jsx
+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(
+
+ {/* 你的应用程序的其余部分在这里 */}
+
+ );
+
+ res.write(html);
+ res.end();
+}
+
+http.createServer(requestHandler)
+ .listen(3000);
+```
+
+
+另见
+---
+
+- [React Router 官网](https://reactrouter.com/)