如何在 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

相关文章

JWT 的应用场景思考

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

阅读更多
用 Beancount 复式记账,Double-Entry!

🤔动机 已经有很多次记账的尝试,包括各种 App 如圈子账本(现已倒闭下架)等。但有几个痛点还没法解决: 大多数都是简单的记载收支,这对手握多个银行账户、信用卡等的我来说,显得不够用 数据安全问题,数据在服务提供者手上,既有隐私担忧,又受制于人,谁知道什么时候就删库跑路了 数据报表,大多数 App 的统计都比较垃,甚至需要 VIP 才能看

阅读更多
图数据库 Cypher 查询语言的子查询 CALL (subquery)

子查询允许将查询组合起来,这在使用UNION或聚合时特别有用。 子查询与封闭查询交互的方式有一些限制: 子查询只能引用外部查询中显式导入的变量。 子查询不能返回与外围查询中变量名称相同的变量。 从子查询返回的所有变量随后都可在外部查询中使用。 WITH导入变量进子句必须:

阅读更多