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

客户端路由
使用 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>
    )
}参考文章
致谢: