如何在 React 中轻松路由?使用 React Router!

开始

tsx
import React from "react";
import ReactDOM from "react-dom/client";
import {
  createBrowserRouter,
  RouterProvider,
} from "react-router-dom";
import Root from "./routes/root";

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

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

root 路由比较特殊。

Not Found

tsx
import ErrorPage from "./error-page";

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

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

嵌套路由

<Contact /> 渲染在 <Root />children(<Outlet />) 中:

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

使用 Layout 也能解决。

注意,必须在 Root 中原来 children 的地方替换或加上 Outlet:

Untitled

客户端路由

使用 Link 组件。

该路由不会重新请求页面,完全由客户端操控

tsx
import { **Link** } from "react-router-dom";

export default function Root() {
  return (
    <>
      <div id="sidebar">
        {/* other elements */}

        <nav>
          <ul>
            <li>
              **<Link to={`contacts/1`}>Your Name</Link>**
            </li>
            <li>
              **<Link to={`contacts/2`}>Your Friend</Link>**
            </li>
          </ul>
        </nav>

        {/* other elements */}
      </div>
    </>
  );
}

加载数据

将数据导入路由组件,支持异步获取

tsx
// ./routes/root.jsx 数据获取方法
import { getContacts } from "../contacts";

export async function loader() {
  const contacts = await getContacts();
  return { contacts };
}

// main.jsx  数据导入 loader
import Root, { loader as rootLoader } from "./routes/root";

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

// ./routes/root.jsx 如何使用导入的数据?
import {
  **useLoaderData**,
} from "react-router-dom";

/* other code */

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

拦截 Form 提交

有时候表单还是有用的~

tsx
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}`);
}

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,**
      },
    ],
  },
]);

重定向

在组件中不起作用,组件中应使用 <link> 进行客户端路由而不是重定向!

或者编程式路由,用 navigate("/login")

tsx
import {
  **redirect**,
} from "react-router-dom";
import { getContacts, createContact } from "../contacts";

export async function action() {
  const contact = await createContact();
  return **redirect(`/contacts/${contact.id}/edit`);**
}

激活 active 链接的样式

使用 NavLink

tsx
import {
  **NavLink**,
} from "react-router-dom";

export default function Root() {
  return (
    <>
      <div id="sidebar">
        {/* other code */}

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

全局等待 Pending UI

当用户浏览应用程序时,React Router将保留旧页面,因为数据正在加载到下一个页面。你可能已经注意到,当你在列表之间点击时,应用程序感觉有点没有反应。让我们为用户提供一些反馈,这样应用程序就不会感到没有响应。

使用 useNavigation 钩子。

tsx
import {
  // existing code
  useNavigation,
} from "react-router-dom";

// existing code

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

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

索引 Index 路由

当一条路由有子路由,并且您位于父路由的路径上时,没有任何内容可渲染,因为没有子路由匹配。您可以将索引路由视为默认的子路由来填充该空间。

tsx
import Index from "./routes/index";

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

浏览器后退

在编辑页面上,我们有一个取消按钮,它还没有做任何事情。我们希望它能做与浏览器的后退按钮相同的事情。

使用 useNavigation 钩子。

tsx
import {
  **useNavigate**,
} from "react-router-dom";

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

  return (
    <Form method="post" id="contact-form">
      {/* existing code */}

      <p>
        <button type="submit">Save</button>
        <button
          type="button"
          **onClick={() => {
            navigate(-1);
          }}**
        >
          Cancel
        </button>
      </p>
    </Form>
  );
}

JSX 风格路由

tsx
import {
  createRoutesFromElements,
  createBrowserRouter,
} from "react-router-dom";

const router = createBrowserRouter(
  createRoutesFromElements(
    <Route
      path="/"
      element={<Root />}
      loader={rootLoader}
      action={rootAction}
      errorElement={<ErrorPage />}
    >
      <Route errorElement={<ErrorPage />}>
        <Route index element={<Index />} />
        <Route
          path="contacts/:contactId"
          element={<Contact />}
          loader={contactLoader}
          action={contactAction}
        />
        <Route
          path="contacts/:contactId/edit"
          element={<EditContact />}
          loader={contactLoader}
          action={editAction}
        />
        <Route
          path="contacts/:contactId/destroy"
          action={destroyAction}
        />
      </Route>
    </Route>
  )
);

Form 相关

获取 get 中的 url params

将 form 改为 Form,使用 Router 组件。

然后:

tsx
export async function loader({ **request** }) {
  **const url = new URL(request.url);
  const q = url.searchParams.get("q");**
  const contacts = await getContacts(q);
  return { contacts };
}

多返回一些值做默认值

tsx
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();**

...

	<input
	    id="q"
	    aria-label="Search contacts"
	    placeholder="Search"
	    type="search"
	    name="q"
	    **defaultValue={q}**
	  />

表单变化时就提交

使用 useSubmit 钩子。

tsx
// existing code
import {
  // existing code
  **useSubmit,**
} from "react-router-dom";

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

  return (
    <>
      <div id="sidebar">
        <h1>React Router Contacts</h1>
        <div>
          <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>
          {/* existing code */}
        </div>
        {/* existing code */}
      </div>
      {/* existing code */}
    </>
  );
}

减少历史记录?replace not push!

tsx
onChange={(event) => {
    const isFirstSearch = q == null;
    **submit(event.currentTarget.form, {
      replace: !isFirstSearch,
    });**
  }}

导航没变化而数据有变

例如点击收藏按钮

使用 useFetcher 钩子。

tsx
const fetcher = useFetcher();

<fetcher.Form method="post">

</fetcher.Form>

常见场景

登录

按钮外包裹客户端路由组件

tsx
<**Link** to="/login">
  <Button type="primary">登录</Button>
</**Link**>

当前路由(路径)?

获取当前路径用 useLocation 钩子。

const location = **useLocation()**.

location.pathname

组件(声明)式路由

使用 <Link><NavLink> 组件

编程(命令)式路由

使用 useNavigate 钩子。

const navigate = useNavigate(); navigate('/home')

路径参数

使用 useParams 钩子。

tsx
<BrowserRouter>
    <Routes>
        <Route path='/foo/:id' element={Foo} />
    </Routes>
</BrowserRouter>

import { useParams } from 'react-router-dom';
export default function Foo(){
    **const params = useParams();**
    return (
        <div>
            <h1>{params.id}</h1>
        </div>
    )
}

参考文章

致谢:

标签 :
相关文章
如何自己本地编译 OpenWrt ?

感觉不如直接下载,然后用转换工具把 img 搞成 vmfs,见linux - VMware安装OpenWrt,工具下载链接https://www.starwindsoftware.com/tmplink/starwindconverter.exe。 关于系统 LEAN 的不行,还是 ImmortalWrt 更好!还可直接下载 vmfs,注意系统日志等级它默认的 debug 可不行。 关于上网控制插件,注意会和广告过滤 ACC 加速等冲突,有舍有得啊,openwrt上网时间控制为什么设置后无效-OPENWRT专版-恩山无线论坛 (right.com.cn)。 老老实实用 clash for windows 了,OpenWrt 很不稳定啊,github 时好时坏。

阅读更多
JWT 的应用场景思考

JWT 的应用场景思考 简述 JWT JWT,全称 JSON Web Token。是一种开放标准,用于在各方之间安全传递信息,它是以 Base64 编码 json 对象的 token 。基于 token 的权限验证,与传统的 Session 认证完全不同,它不需要服务端保持 Session 记录,连用户状态都不需要关心。一旦用户登录网站,服务器就会生成 token,之后客户端每次登录时在HTTP的头信息中带上 token 即可。

阅读更多
Snapcast 多房间音频控制一体化

动机 想要手头的两台 MBP 能同时无延迟播放音频,拒绝使用直播流的形式。 Tip update at 2025-04-13

阅读更多