路由

react 前端路由的本质是路径和组件的一一对应关系, 即不同的 path 对应不同的 component, 访问不同路径时渲染对应的组件.

安装

npm install --save react-router-dom

快速使用

  1. 引入 createBrowserRouter, RouterProvider

    • createBrowserRouter: 创建路由实例, 在方法中定义路由 path 和组件的对应关系

    • RouterProvider: 作为一个组件渲染, 并且传入 createBrowserRouter 方法执行后生成的 router 实例

  2. 调用 createBrowserRouter, 生成 router 实例

  3. 渲染 RouterProvider 组件并传入 router 实例

src/index.js

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

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

const router = createBrowserRouter([
  {
    path: "/",
    element: <div>this is home</div>,
  },
  {
    path: "/login",
    element: <div>this is login</div>,
  },
]);

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>
);

npm start 后在浏览器输入 localhost:3000/localhost:3000/login 即可看到效果

抽离单独组件和路由独立文件

目录结构 tree -I node_modules

.
├── package.json
├── package-lock.json
├── public
│   ├── favicon.ico
│   └── index.html
├── README.md
└── src
    ├── index.js
    ├── page
    │   ├── About.jsx
    │   ├── Home.jsx
    │   └── Login.jsx
    └── router
        └── index.jsx

src/index.js

import React from "react";
import ReactDOM from "react-dom/client";
import { RouterProvider } from "react-router-dom";
import router from "./router"; // 引入 router 路由组件

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>
);

src/router/index.jsx

// 配置路由对应关系
import { createBrowserRouter } from "react-router-dom";
import Home from "../page/Home";
import Login from "../page/Login";
import About from "../page/About";

const router = createBrowserRouter([
  {
    path: "/",
    element: <Home />,
  },
  {
    path: "/login",
    element: <Login />,
  },
  {
    path: "/about",
    element: <About />,
  },
]);

export default router;

src/page/Home.jsx

const Home = () => {
  return <div>this is home</div>;
};

export default Home;

两种路由模式

react 函数 对应的路由模式 url 表现 底层原理 是否需要后端支持 兼容性
createBrowerRouter history url/login history 对象 + pushState 需要 ie10
createHashRouter hash url/#/login 监听 hashchange 事件 不需要 ie8

src/router/index.jsx

// 引入 createHashRouter
import { createBrowserRouter, createHashRouter } from "react-router-dom";
import Home from "../page/Home";
import Login from "../page/Login";
import About from "../page/About";

// hash 模式
const router = createHashRouter([
  {
    path: "/",
    element: <Home />,
  },
  {
    path: "/login",
    element: <Login />,
  },
  {
    path: "/about",
    element: <About />,
  },
]);

export default router;

url 从 localhost:3000/login 变为 localhost:3000/#/login

编程式导航

通过 js 编程的方式进行路由页面跳转, 比如从首页跳转到登陆页

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

const Login = () => {
  // 执行 useNavigate, 得到跳转函数
  const navigate = useNavigate();
  const goToAbout = () => {
    // 执行跳转函数
    navigate("/about");
  };

  return (
    <div>
      this is login
      <button onClick={goToAbout}>go to about</button>
    </div>
  );
};

export default Login;

localhost:3000/#/login 点击 goToAbout 按钮即可跳转到 localhost:3000/#/about

这是叠加状态, 即 /about 页面叠加在 /login

如果在跳转时, 想要替换记录, 可以在 navigate 跳转函数的第二个参数加上 {replace: true}

const goToAbout = () => {
  navigate("/about", { replace: true });
};

这时候 localhost:3000/#/login 点击 goToAbout 跳转到 localhost:3000/#/about,

但是在浏览器的后退按钮时, 会回到 localhost:3000 而不是 localhost:3000/#/login

/about 页面替换/login 页面

路由跳转传参

  1. searchParams 传参

    scr/page/Login.jsx

    import { useNavigate } from "react-router-dom";
    
    const Login = () => {
      const navigate = useNavigate();
      const goToAbout = () => {
        // 路由传参方式一: searchParams
        // 在跳转路由时拼接参数
        navigate("/about?id=1001");
      };
    
      return (
        <div>
          this is login
          <button onClick={goToAbout}>go to about</button>
        </div>
      );
    };
    
    export default Login;
    

    登陆 localhost:3000/login 页面后, 点击 go to about 按钮, 会跳转到 localhost:3000/about?id=1001

    在 About 组件里获取参数

    src/page/About.jsx

    // 导入 useSearchParam 函数
    import { useSearchParams } from "react-router-dom";
    
    const About = () => {
      // 获取通过 searchParams 传参过来的 id 参数
      const [params] = useSearchParams();
      const id = params.get("id");
      return <div>this is about {id}</div>;
    };
    
    export default About;
    

    传递多个参数

    src/page/Login.jsx

    import { useNavigate } from "react-router-dom";
    
    const Login = () => {
      const navigate = useNavigate();
      const goToAbout = () => {
        navigate("/about?id=1001&name=pyq");
      };
    
      return (
        <div>
          this is login
          <button onClick={goToAbout}>go to about</button>
        </div>
      );
    };
    
    export default Login;
    

    src/page/About.jsx

    import { useSearchParams } from "react-router-dom";
    
    const About = () => {
      const [params] = useSearchParams();
      const id = params.get("id");
      const name = params.get("name");
      return (
        <div>
          this is about {id} {name}
        </div>
      );
    };
    
    export default About;
    
  2. params 传参

    scr/router/index.jsx

    import { createBrowserRouter, createHashRouter } from "react-router-dom";
    import Home from "../page/Home";
    import Login from "../page/Login";
    import About from "../page/About";
    
    const router = createBrowserRouter([
      {
        path: "/",
        element: <Home />,
      },
      {
        path: "/login",
        element: <Login />,
      },
      {
        path: "/about/:id", // 首先用 id 占位符
        element: <About />,
      },
    ]);
    
    export default router;
    

    src/page/Login.jsx

    import { useNavigate } from "react-router-dom";
    
    const Login = () => {
      const navigate = useNavigate();
      const goToAbout = () => {
        navigate("/about/1001"); // 在跳转时, 加上参数
      };
    
      return (
        <div>
          this is login
          <button onClick={goToAbout}>go to about</button>
        </div>
      );
    };
    
    export default Login;
    

    scr/page/About.jsx

    import { useParams } from "react-router-dom";
    
    const About = () => {
      // 接受时, 使用 useParams
      const params = useParams();
      return <div>this is about {params.id}</div>;
      // 也可以 const { id } = useParams();
      // const { id } = useParams();
      // return <div>this is about {id}</div>;
    };
    
    export default About;
    

嵌套路由

如果当前组件的内容, 是在另一个组件模板中的局部进行切换, 那它就是一个嵌套路由

在路由表中, 通过 children 属性进行二级路由配置

通过内置组件 Outlet 渲染二级路由组件

使用内置组件 Link 进行声明式导航配置

scr/router/index.jsx

import { createBrowserRouter } from "react-router-dom";
import Layout from "../page/Layout";
import Login from "../page/Login";
import About from "../page/About";
import Article from "../page/Article";
import Board from "../page/Board";

const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    // 二级路由配置
    children: [
      {
        path: "article",
        element: <Article />,
      },
      {
        path: "board",
        element: <Board />,
      },
    ],
  },
  {
    path: "/login",
    element: <Login />,
  },
  {
    path: "/about/:id",
    element: <About />,
  },
]);

export default router;

src/page/Layout.jsx

import { Link, Outlet } from "react-router-dom";
const Layout = () => {
  return (
    <div>
      这是一级路由 layout 对应的模板
      <div>
        <p>
          <Link to="/board">看板</Link>
          <Link to="/article">文章</Link>
        </p>
      </div>
      <div>
        <p>这里是二级路由的出口位置</p>
        <Outlet />
      </div>
    </div>
  );
};

export default Layout;

默认二级路由渲染

如果没有设置默认的二级路由

localhost:3000 不会渲染二级路由的组件,

只有访问 localhost:3000/board, localhost:3000/article 才会渲染二级路由组件

设置了默认的二级路由就能实现 localhost:3000 直接渲染二级组件

src/router/index.jsx

import { createBrowserRouter } from "react-router-dom";
import Layout from "../page/Layout";
import Login from "../page/Login";
import About from "../page/About";
import Article from "../page/Article";
import Board from "../page/Board";

const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    children: [
      {
        path: "article",
        element: <Article />,
      },
      {
        // 删除 path: "/board"
        // 加上 index: true
        // 将 Board 设置为默认的二级路由组件
        index: true,
        element: <Board />,
      },
    ],
  },
  {
    path: "/login",
    element: <Login />,
  },
  {
    path: "/about/:id",
    element: <About />,
  },
]);

export default router;

src/page/Layout.jsx

import { Link, Outlet } from "react-router-dom";
const Layout = () => {
  return (
    <div>
      这是一级路由 layout 对应的模板
      <div>
        <p>
          {/* 将 Board 的路径改为 /, 因为这是默认的二级路由组件 */}
          <Link to="/">看板</Link>
          <Link to="/article">文章</Link>
        </p>
      </div>
      <div>
        <p>这里是二级路由的出口位置</p>
        <Outlet />
      </div>
    </div>
  );
};

export default Layout;

404 页面配置

src/router/index.jsx

import { createBrowserRouter } from "react-router-dom";
// 省略
import NotFound from "../page/NotFound";

const router = createBrowserRouter([
  // 省略
  {
    path: "*",
    element: <NotFound />,
  },
]);

export default router;

src/page/NotFound.jsx

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

const NotFound = () => {
  return (
    <div>
      你访问的页面飞往了月球
      <Link to="/">回到首页</Link>
    </div>
  );
};

export default NotFound;

参考