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

相关文章

Neo4j: 图数据基本概念、语法与增删改查

Neo4j 简介 Neo4j 使用属性图(Property Graph)模型1。 一个图包含节点(Objects)和边(Relationships)。 Neo4j 的属性图模型包含了: 节点 节点标签:用于区分节点的类型,0个或多个 边:源节点与目标节点之间的一条有向边 边类型:用于区分边的类型,有且仅有1个 属性:节点和边都可以有属性(键值对)用于描述节点和边 注意这些名词的英文:node、relationship、label(节点可以有0个或多个标签)、type(边只有一个类型)、property

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

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

阅读更多
Remarkable2 配置中文字体与其他 Tips

动机 每次更新系统之后,我的字体就没了,我哭😭 主旨 导入文档 下面尝试导入 PDF、EPUB 来看看阅读效果。首先肯定要去官方的支持网站,看看用户手册。 官方的导入方案中,总的来说就两种,一个是通过它的云同步服务,在客户端程序上导入,另一个就是通过 USB 连接到电脑,通过浏览器网页导入。

阅读更多