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

目录

开始

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

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 />) 中:

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

使用 Layout 也能解决。

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

Untitled

客户端路由

使用 Link 组件。

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

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>
    </>
  );
}

加载数据

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

// ./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 提交

有时候表单还是有用的~

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")

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

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 钩子。

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 路由

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

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 钩子。

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 风格路由

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 组件。

然后:

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

多返回一些值做默认值

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 钩子。

// 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!

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

导航没变化而数据有变

例如点击收藏按钮

使用 useFetcher 钩子。

const fetcher = useFetcher();

<fetcher.Form method="post">

</fetcher.Form>

常见场景

登录

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

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

当前路由(路径)?

获取当前路径用 useLocation 钩子。

const location = **useLocation()**.

location.pathname

组件(声明)式路由

使用 <Link><NavLink> 组件

编程(命令)式路由

使用 useNavigate 钩子。

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

路径参数

使用 useParams 钩子。

<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>
    )
}

参考文章

致谢:

标签 :
comments powered by Disqus

相关文章

BlockSuite 一款 Block Style 的编辑器,仅仅是编辑器!

先说结论 我的博客不太适合用 BlockSuite 作为编辑器,因为: 不知道如何渲染,我可以编辑,但是如何展示给匿名用户看? SEO 不太明确,也想着转成 HTML 来做,但是成本太高? 其他博主的调研:https://blog.nineya.

阅读更多

毕业论文中公式太多了,手动维护起来非常复杂,如何进行自动编号?

第一步:在标题上插入两个域代码 SEQ eq \h SEQ list \r \h SEQ 相当于我们编程中的关键字,eq和list相当于变量的名字,可以自己随便命名,\h代表hide,即隐藏,\r代表reset 第二步:在公式中使用变量 如果有多个

阅读更多

最小生成树

最小生成树:Kruskal算法和Prim算法的实现 这里直接给出离散数学中的几个定理和推论: 无向图G具有生成树当且仅当G连通. G为n阶m条边的无向连通图,则 m >= n - 1. G是树 $\Leftrightarrow$ G中任意两个顶点之间存在唯一

阅读更多