Skip to content

React 路由知识点汇总

安装

bash

npm install react-router-dom localforage match-sorter sort-by -S

# 如果是ts项目 下面的需要安装

npm i --save-dev @types/sort-by

新建路由目录

bash
src
├── routes
   ├── router.jsx

router.jsx

jsx
import React from "react";
import { createBrowserRouter } from "react-router-dom";

const router = createBrowserRouter([
  // 2. 路由配置
  {
    path: "/",
    element: <h1>欢迎你,ReactRouter</h1>,
  },
]);

export default router;

引入 router

jsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { RouterProvider } from "react-router-dom";
import router from "./routes/router";
createRoot(document.getElementById("root")).render(
  <RouterProvider router={router}></RouterProvider>,
);

警告

这样运行起来就能看见欢迎你,ReactRouter

失败处理(自定义)

  • 失败处理:当路由匹配不到任何路径时,会触发失败处理
bash
src
├── error.jsx

(1) 新建 error.jsx

jsx
function ErrorPage() {
  return (
    <div>
      <h1>404 Not Found</h1>
    </div>
  );
}

export default ErrorPage;

(2) 绑定到路由上面

  • router.jsx 里面修改
jsx
import React from "react";

import { createBrowserRouter } from "react-router-dom";

import ErrorPage from "../error";

const router = createBrowserRouter([
  // 2. 路由配置
  {
    path: "/",
    element: <h1>欢迎你,ReactRouter</h1>,
    errorElement: <ErrorPage></ErrorPage>,
  },
]);

export default router;

通配错误页面

jsx
import React from "react";

import { createBrowserRouter } from "react-router-dom";

import ErrorPage from "../error";

const router = createBrowserRouter([
  // 2. 路由配置 放到最后一个,匹配不到就走这个
  {
    path: "/",
    element: <h1>欢迎你,ReactRouter</h1>,
  },
  {
    path: "*",
    element: <ErrorPage></ErrorPage>,
  },
]);

export default router;

重定向

  • 重定向:当路由匹配到某个路径时,会自动跳转到另一个路径
tsx
import React from "react";

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

import ErrorPage from "../error";

const router = createBrowserRouter([
  // 2. 路由配置 放到最后一个,匹配不到就走这个
  {
    path: "/",
    element: <h1>欢迎你,ReactRouter</h1>,
  },
  {
    path: "/403",
    element: <Navigate to="*" />,
  },
  {
    path: "*",
    element: <ErrorPage></ErrorPage>,
  },
]);

export default router;

嵌套路由

  • 嵌套路由:路由里面嵌套路由,子路由
bash
src
├── layOut
   ├── Layout.jsx
├── views
   ├── index.jsx
   ├── Page1.jsx
   ├── Page2.jsx

LayOut.jsx

  • Outlet 组件:用于渲染子路由
jsx
import { Outlet } from "react-router-dom";
function LayOut() {
  return (
    <div>
      <h1>Layout</h1>
      <p>这是Layout组件</p>
      <div>-------下面是子路由----------</div>
      <Outlet />
    </div>
  );
}

export default LayOut;

index.jsx|Page1.jsx|Page2.jsx

  • 默认页面
jsx
function IndexPage() {
  return <div>默认页面</div>;
}

export default IndexPage;
  • Page1.jsx
jsx
function Page1() {
  const gotourl = () => {
    console.log("跳转到路由2");
  };
  const gotourlcanshu = () => {
    console.log("携带参数跳转到路由2");
  };
  return (
    <div>
      路由1的页面
      <button onClick={gotourl}>跳转到路由2</button>
      <button onClick={gotourlcanshu}>携带参数跳转到路由2</button>
    </div>
  );
}

export default Page1;
  • Page2.jsx
jsx
function Page2() {
  const gotourl = () => {
    console.log("跳转到路由1");
  };
  const gotourlcanshu = () => {
    console.log("携带参数跳转到路由1");
  };
  return (
    <div>
      路由1的页面
      <button onClick={gotourl}>跳转到路由2</button>
      <button onClick={gotourlcanshu}>携带参数跳转到路由2</button>
    </div>
  );
}

export default Page2;

Router.jsx

修改 router.jsx

jsx
import React from "react";

import { createBrowserRouter } from "react-router-dom";

import ErrorPage from "../error";

import LayOut from "../layOut/Layout";

import Page1 from "../views/Page1";

import Page2 from "../views/Page2";

// 默认页面

import IndexPage from "../views/index";

const router = createBrowserRouter([
  // 2. 路由配置
  {
    path: "/",
    element: <LayOut></LayOut>,
    errorElement: <ErrorPage></ErrorPage>,
    children: [
      {
        index: true,
        element: <IndexPage></IndexPage>,
      },
      {
        path: "page1",
        element: <Page1></Page1>,
      },
      {
        path: "page2",
        element: <Page2></Page2>,
      },
    ],
  },
]);

export default router;

注意

增加一个属性index:true 表示默认子路由.访问 / 时,会渲染 IndexPage 组件.

跳转路由

  • Link 组件:用于跳转路由 但是获取不到样式

  • NavLink 组件:用于跳转路由 获取到样式,并且可以自定义样式

  • 方法

jsx
<Link to={`/page1`}>link跳转到page1</Link>

注意

NavLink 能判断当前路由是否是当前点击的路由,如果是,则添加 active 类名,否则添加 pending 类名

JSX
 <NavLink
    to={`/page1`}
    className={({ isActive, isPending }) =>
      isActive ? "active" : isPending ? "pending" : ""
      }
>
  NavLink点击我跳转
</NavLink>

方法跳转

  • useNavigate
jsx
// 跳转函数
const navigate = useNavigate();
const gotourl = () => {
  console.log("跳转到路由2");
  navigate(`/page2`);
};

获取参数和路由状态

通过路由参数获取

警告

格式如下: xxx/:id

获取参数通过 useParams

  • 新建一个路由
jsx
{
  path: "/page2/:id",
  element: <Page2></Page2>
},
  • 在组件中获取参数
js
import { useParams } from "react-router-dom";
function Page2() {
  const Params = useParams();
  console.log("*****");
  console.log(Params.id);
  console.log("*******");
}

第二种通过参数来获取

警告

格式如下: xxx?id=xxx

获取参数通过 URL

  • 获取参数
jsx
// 这样就能获取到了
const location = useLocation();
console.log(location);
const searchParams = new URLSearchParams(location.search);
const paramsObject = Object.fromEntries(searchParams.entries());
console.log(paramsObject);

第三种 通过参数来获取

警告

格式如下: xxx?id=xxx

获取参数通过 URL

jsx
import { useSearchParams } from "react-router-dom";
const [searchParams] = useSearchParams();
console.log(searchParams);
const id = searchParams.get("id");

路由跳转传递参数

在 React Router v6 中,可以使用 useNavigate 钩子实现编程式页面跳转,并在跳转时传递参数。主要有两种方式:state 状态参数(隐式)URL 搜索参数(显式)

引入并实例化 useNavigate

jsx
// 从 react-router-dom 中引入 useNavigate 钩子
import { useNavigate } from "react-router-dom";

function HomePage() {
  // 实例化 navigate 函数,用于编程式跳转
  const navigate = useNavigate();

  return <div>首页</div>;
}

方式一:state 状态参数传递(隐式传参)

参数不会显示在 URL 中,通过 useLocation 钩子获取。适合传递不希望暴露在地址栏的数据。

跳转发起页面:

jsx
import { useNavigate } from "react-router-dom";

export default function HomePage() {
  // 实例化 navigate 跳转函数
  const navigate = useNavigate();

  // 点击按钮时触发跳转,通过 state 隐式携带 id 和 name 参数
  const handleClick = () => {
    navigate("/detail", {
      state: {
        id: 1001, // id:推荐 number 或 string 类型
        name: "张三", // name:string 类型
      },
    });
  };

  return (
    <div>
      <h2>首页</h2>
      {/* 点击按钮跳转到详情页并携带 state 参数 */}
      <button onClick={handleClick}>跳转到详情页</button>
    </div>
  );
}

目标页面接收参数:

jsx
// 引入 useLocation 钩子,用于读取路由 state
import { useLocation } from "react-router-dom";

export default function DetailPage() {
  // 获取路由 location 对象,其中包含跳转时传递的 state
  const location = useLocation();

  // 从 state 中解构出 id 和 name;使用 || {} 兜底,防止直接访问页面时 state 为 null 报错
  const { id, name } = location.state || {};

  return (
    <div>
      <h2>详情页</h2>
      <p>接收到的 id:{id}</p>
      <p>接收到的 name:{name}</p>
    </div>
  );
}

方式二:URL 搜索参数传递(显式传参)

参数拼接在 URL 中(如 /detail?id=1001&name=张三),通过 useSearchParams 钩子解析。适合需要分享、收藏链接的场景。

跳转发起页面:

jsx
import { useNavigate } from "react-router-dom";

export default function HomePage() {
  // 实例化 navigate 跳转函数
  const navigate = useNavigate();

  // 点击按钮时触发跳转,将 id 和 name 拼接为 URL 查询参数
  const handleClick = () => {
    // 定义要传递的参数;id 推荐 number 或 string 类型,name 为 string 类型
    const id = 1001;
    const name = "张三";
    // 拼接为查询字符串,中文字符会被自动编码
    navigate(`/detail?id=${id}&name=${name}`);
  };

  return (
    <div>
      <h2>首页</h2>
      {/* 点击按钮跳转到详情页并携带 URL 查询参数 */}
      <button onClick={handleClick}>跳转到详情页</button>
    </div>
  );
}

目标页面接收参数:

jsx
// 引入 useSearchParams 钩子,用于解析 URL 查询参数
import { useSearchParams } from "react-router-dom";

export default function DetailPage() {
  // 获取搜索参数对象 searchParams(setSearchParams 未用到可不解构)
  const [searchParams] = useSearchParams();

  // 通过 get 方法读取指定参数;注意:读取到的值均为 string 类型
  const id = searchParams.get("id"); // 得到字符串 "1001",如需数值需手动 Number(id)
  const name = searchParams.get("name"); // 得到字符串 "张三"

  return (
    <div>
      <h2>详情页</h2>
      <p>接收到的 id:{id}</p>
      <p>接收到的 name:{name}</p>
    </div>
  );
}

参数安全注意事项

  • 敏感参数(如密码、token、手机号等)不要通过 URL 搜索参数传递,因为参数会明文显示在地址栏、浏览器历史记录和服务器日志中,存在泄露风险,此类数据应使用 state 或其他安全方式传递。
  • useSearchParams 读取到的参数均为字符串类型,即使传入的是数字也需自行转换类型。

参数类型约定

  • id:推荐使用 numberstring 类型
  • name:推荐使用 string 类型
  • 通过 state 传递时参数保持原始类型;通过 URL 搜索参数传递时会被序列化为字符串

路由状态

  • 一般写在根组件中,作为判断 loading 是否完成的依据

警告

  • 通过 useNavigation 来获取路由状态
  • 判断是 状态是 loading 还是其他的,状态是 loading 就加载 loading 的类
jsx
export default function Root() {
  // 路由信息对象
  const navigation = useNavigation(); // 路由信息对象
  return (
    <div id="detail" className={navigation.sate == "loading" ? "loading" : ""}>
      <Outlet />
    </div>
  );
}

Loader 和 action

警告

  1. loader 设计的理念是用户没进入路由之前,先获取到异步请求的数据,然后渲染到页面上

  2. action 设计的理念是拦截用户通过 Form 组件包裹的数据转发到 action 上面

  3. 表单提交动作处理,不能是 get,只能是 post,del,put,它会通过 Form 拦截,找到下面的 action 属性来匹配,get 请求会触发 loader

loader

不获取参数

警告

  1. 页面里面暴露出 loader,必须用{}包裹

  2. 组件里面通过使用 useLoaderData 来获取数据

  • page2.jsx
jsx
import { useLoaderData } from "react-router-dom";
export const loader = async () => {
  // const contact = await xxxx() 你自己获取数据的方法
  const contact = { name: "李四" };
  console.log(contact);
  return { contact }; // 返回联系人详细信息
};

function Page2() {
  const { contact } = useLoaderData();
  return (
    <div>
      路由2的页面
      <div>{contact.name}</div>
    </div>
  );
}

export default Page2;
  • 绑定到 router 上面,配置 loader 属性
jsx
import React from "react";

import { createBrowserRouter } from "react-router-dom";

import ErrorPage from "../error";

import LayOut from "../layOut/Layout";

import Page1 from "../views/Page1";

import Page2, { loader as page2Loader } from "../views/Page2";

// 默认页面

import IndexPage from "../views/index";

const router = createBrowserRouter([
  // 2. 路由配置
  {
    path: "/",
    element: <LayOut></LayOut>,
    errorElement: <ErrorPage></ErrorPage>,
    children: [
      {
        index: true,
        element: <IndexPage></IndexPage>,
      },
      {
        path: "/page1",
        element: <Page1></Page1>,
      },
      {
        path: "/page2",
        element: <Page2></Page2>,
        loader: page2Loader,
      },
    ],
  },
]);

export default router;

获取参数

    1. 创建新的路由
js
// 引入loader
import Page2, { loader as page2Loader } from "../views/Page2";
// 创建新的路由
{
  path: "/page2/:id",
  element: <Page2></Page2>,
  loader: page2Loader,
},
    1. 修改 loader

page2.jsx 修改 loader,通过 params 获取参数

js
export const loader = async ({ params }) => {
  // const contact = await xxxx() 你自己获取数据的方法
  if (params.id) {
    console.log(params.id);
    const contact = { name: `李四---${params.id}` };
    console.log(contact);
    return { contact };
  } else {
    const contact = { name: "李四" };
    console.log(contact);
    return { contact };
  }
};
    1. 组件渲染
jsx
function Page2() {
  const { contact } = useLoaderData();
  return (
    <div>
      路由2的页面
      <div>{contact.name}</div>
    </div>
  );
}

export default Page2;

action 用法

action 是拦截用户通过 Form 组件包裹的数据转发到 action 上面

创建 action

  • page2.jsx 里面创建 action
jsx
export const action = async ({ request, params }) => {
  const formData = await request.formData();
  const updates = Object.fromEntries(formData);
  console.log(updates); // 获取到你提交的数据
  // await updateContact(params.contactId, updates); // 这里就是你发送走的数据
  // return redirect(`/contacts/${params.contactId}`);
};

组件里面使用

  • 不加 action 的话 默认就是找的本页面的
jsx
<Form method="post">
  <input type="text" name="name" />
  <button type="submit">提交</button>
</Form>
  • 如果加 action 的 话 找的就是本页面+action 的

路径: /page2/edit

jsx
<Form method="post" action="edit">
  <input type="text" name="name" />
  <button type="submit">提交</button>
</Form>

绑定到 router 上面

jsx
// 引入
import Page2, {
  loader as page2Loader,
  action as page2Action,
} from "../views/Page2";
// 路由里面加载
{
  path: "/page2/:id",
  element: <Page2></Page2>,
  loader: page2Loader,
  action: page2Action,
},

路由懒加载

警告

  1. 懒加载必须使用异步函数 lazy 返回出 loader,action,Component

  2. default 里面的名称 必须和下面的组件名称一致

修改 router

js
const router = createBrowserRouter([
  {
    path: "/",
    element: <Root />,
    // 进入路由器前面 就加载数据,然后在渲染数据
    loader: rootloader,
    // 表单提交动作处理,不能是get,只能是post,del,put,它会通过Form拦截,找到下面的action属性来匹配,get请求会触发loader
    action: rootaction,
    // 加载错误组件
    errorElement: <ErrorPage />,
    // 嵌套路由 还得在根组件里面加入Outlet
    children: [
      // 找到默认页面
      {
        index: true,
        element: <Index></Index>,
      },
      {
        path: "contacts/:contactId",
        async lazy() {
          const {
            default: Contact,
            loader,
            action,
          } = await import("../routes/contact");
          return {
            Component: Contact,
            loader,
            action,
          };
        },
      },
      {
        path: "contacts/:contactId/edit",
        async lazy() {
          const { default: EditCom, action } = await import("./edit");
          // 因为这里引用了别的组件的logo
          const { loader } = await import("../routes/contact");
          return {
            Component: EditCom,
            loader,
            action,
          };
        },
      },
      {
        path: "contacts/:contactId/destroy",
        element: <EditComponent />,
        action: destroyAction,
        // 单独配置错误信息,局部优先于全局
        errorElement: <h1>删除失败</h1>,
      },
    ],
  },
]);