夜猫子的知识栈 夜猫子的知识栈
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

夜猫子

前端练习生
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 核心概念

  • 高级指引

  • Hook

  • 案例演示

  • Router

    • 详细使用示例
      • 动态路由
      • 路由传参
    • Redux

    • Jotai

    • Mobx

    • 其他

    • 《React》笔记
    • Router
    夜猫子
    2022-11-04
    目录

    详细使用示例

    # react-router-dom 详细使用

    注意:这里使用的时最新的 v6 版本,已完全弃用了原先的 react-router 库,较 v5 版本有很大的改动。

    # 一、基本使用

    1. 首先安装依赖
    npm i react-router-dom
    
    1
    1. 引入实现路由所需的组件,以及页面组件
    import { BrowserRouter, Routes, Route } from "react-router-dom";
    import Foo from "./Foo";
    import Bar from "./Bar";
    function App() {
      return (
        <BrowserRouter>
          <Routes>
            <Route path="/foo" element={<Foo />} />
            <Route path="/bar" element={<Bar />} />
          </Routes>
        </BrowserRouter>
      );
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    • path:路径
    • element:要渲染的组件

    注意:BrowserRouter组件最好放在最顶层所有组件之外,这样能确保内部组件使用 Link 做路由跳转时不出错

    # 二、路由跳转

    在跳转路由时,如果路径是/开头的则是绝对路由,否则为相对路由,即相对于当前 URL进行改变

    # 2.1 Link 组件

    Link组件只能在Router内部使用,因此使用到Link组件的组件一定要放在顶层的 Router 之内

    import { Link } from "react-router-dom";
    
    <Link to="foo">to foo</Link>;
    
    1
    2
    3

    # 2.2 NavLink 组件

    • NavLink组件和Link组件的功能是一致的,区别在于可以判断其to属性是否是当前匹配到的路由
    • NavLink组件的style或className可以接收一个函数,函数接收一个含有isActive字段的对象为参数,可根据该参数调整样式
    import { NavLink } from "react-router-dom";
    
    function Foo() {
      return (
        <NavLink style={({ isActive }) => ({ color: isActive ? "red" : "#fff" })}>
          Click here
        </NavLink>
      );
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    # 2.3 编程式跳转

    使用useNavigate钩子函数生成navigate函数,可以通过 JS 代码完成路由跳转

    useNavigate`取代了原先版本中的`useHistory
    
    1
    import { useNavigate } from 'react-router-dom';
    
    function Foo(){
        const navigate = useNavigate();
        return (
            // 上一个路径:/a;    当前路径: /a/a1
            <div onClick={() => navigate('/b')}>跳转到/b</div>
            <div onClick={() => navigate('a11')}>跳转到/a/a1/a11</div>
            <div onClick={() => navigate('../a2')}>跳转到/a/a2</div>
            <div onClick={() => navigate(-1)}>跳转到/a</div>
        )
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    • 可以直接传入要跳转的目标路由(可以使用相对路径,语法和 JS 相同)
    • 传入-1表示后退

    # 四、动态路由参数

    # 4.1 路径参数

    • 在Route组件中的path属性中定义路径参数
    • 在组件内通过useParams hook 访问路径参数
    <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>
      );
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    # 路径匹配规则

    当URL同时匹配到含有路径参数的路径和无参数路径时,有限匹配没有参数的”具体的“(specific)路径。

    <Route path="teams/:teamId" element={<Team />} />
    <Route path="teams/new" element={<NewTeamForm />} />
    
    1
    2

    如上的两个路径,将会匹配 teams/new 。

    路径的正则匹配已被移除。

    # 兼容类组件

    在以前版本中,组件的props会包含一个match对象,在其中可以取到路径参数。

    但在最新的 6.x 版本中,无法从 props 获取参数。

    并且,针对类组件的 withRouter 高阶组件已被移除。因此对于类组件来说,使用参数有两种兼容方法:

    1. 将类组件改写为函数组件
    2. 自己写一个 HOC 来包裹类组件,用 useParams 获取参数后通过 props 传入原本的类组件

    # 4.2 search 参数

    • 查询参数不需要在路由中定义
    • 使用 useSearchParams hook 来访问和修改查询参数。其用法和 useState 类似,会返回当前对象和更改它的方法
    • 使用 setSearchParams 时,必须传入所有的查询参数,否则会覆盖已有参数
    import { useSearchParams } from "react-router-dom";
    
    // 当前路径为 /foo?id=12
    function Foo() {
      const [searchParams, setSearchParams] = useSearchParams();
      console.log(searchParams.get("id")); // 12
      setSearchParams({
        name: "foo",
      }); // /foo?name=foo
      return <div>foo</div>;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    # 五、嵌套路由

    # 5.1 路由定义

    通过嵌套的书写Route组件实现对嵌套路由的定义。

    path 开头为 / 的为绝对路径,反之为相对路径。

    <Routes>
      <Route path="/" element={<Home />}></Route>
      <Route path="/father" element={<Father />}>
        <Route path="child" element={<Child />}></Route>
        <Route path=":name" element={<Another />}></Route>
      </Route>
    </Routes>
    
    1
    2
    3
    4
    5
    6
    7

    # 5.2 在父组件中展示

    在父组件中使用Outlet来显示匹配到的子组件

    import { Outlet } from "react-router-dom";
    function Father() {
      return (
        <div>
          // ... 自己组件的内容 // 留给子组件Child的出口
          <Outlet />
        </div>
      );
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    # 5.3 在组件中定义

    可以在任何组件中使用 Routes 组件,且组件内的Routes中,路径默认带上当前组件的路径作为前缀。

    注意:此时定义父组件的路由时,要在后面加上 /* ,否则父组件将无法渲染。

    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="dashboard/*" element={<Dashboard />} />
    </Routes>
    function Dashboard() {
      return (
        <div>
          <p>Look, more routes!</p>
          <Routes>
            <Route path="/" element={<DashboardGraphs />} />
            <Route path="invoices" element={<InvoiceList />} />
          </Routes>
        </div>
      );
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    # 六、默认路由

    定义: 在嵌套路由中,如果 URL 仅匹配了父级 URL,则Outlet中会显示带有index属性的子路由。可以使用在路由的任何层级

    <Routes>
      <Route path="/foo" element={Foo}>
        <Route index element={Default}></Route>
        <Route path="bar" element={Bar}></Route>
      </Route>
    </Routes>
    
    1
    2
    3
    4
    5
    6
    • 当 url 为/foo时:Foo 中的 Outlet 会显示 Default 组件
    • 当 url 为/foo/bar时:Foo 中的 Outlet 会显示为 Bar 组件

    # 七、全匹配路由

    定义: path属性取值为*时,可以匹配任何(非空)路径,该匹配拥有最低的优先级。可以用于设置 404 页面。

    <Routes>
      <Route path="/foo" element={Foo}>
        <Route path="bar" element={Bar}></Route>
        <Route path="*" element={NotFound}></Route>
      </Route>
    </Routes>
    
    1
    2
    3
    4
    5
    6

    # 八、多组路由

    通常,一个应用中只有一个Routes组件。

    但根据实际需要也可以定义多个路由出口(如:侧边栏和主页面都要随 URL 而变化)

    <Router>
      <SideBar>
        <Routes>
          <Route></Route>
        </Routes>
      </SideBar>
      <Main>
        <Routes>
          <Route></Route>
        </Routes>
      </Main>
    </Router>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    # 九、路由重定向

    当在某个路径/a下,要重定向到路径/b时,可以通过Navigate组件进行重定向到其他路径

    等价于以前版本中的Redirect组件

    import { Navigate } from "react-router-dom";
    function A() {
      return <Navigate to="/b" />;
    }
    
    1
    2
    3
    4

    # 十、布局路由

    当多个路由有共同的父级组件时,可以将父组件提取为一个没有 path 和 index 属性的Route组件(Layout Route)

    <Route element={<PageLayout />}>
        <Route path="/privacy" element={<Privacy />} />
        <Route path="/tos" element={<Tos />} />
      </Route>
    
    1
    2
    3
    4

    这种写法等价于:

    <Route
      path="/privacy"
      element={
        <PageLayout>
          <Privacy />
        </PageLayout>
      }
    />
    <Route
      path="/tos"
      element={
        <PageLayout>
          <Tos />
        </PageLayout>
      }
    />
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    # 十一、订阅和操作 history stack的原理

    浏览器会记录导航堆栈,以实现浏览器中的前进后退功能。在传统的前端项目中,URL的改变意味着向服务器重新请求数据。

    在现在的客户端路由( client side routing )中,可以做到编程控制URL改变后的反应。如在点击a标签的回调函数中使用 event.preventDefault() 阻止默认事件,此时URL的改变不会带来任何UI上的更新。

    <a
      href="/contact"
      onClick={(event) => {
        // stop the browser from changing the URL and requesting the new document
        event.preventDefault();
        // push an entry into the browser history stack and change the URL
        window.history.pushState({}, undefined, "/contact");
      }}
    />
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    # 11.1 History对象

    浏览器没有直接提供监听URL改变(push、pop、replace)的接口,因此 react-router 对原生的 history 对线进行了包装,提供了监听URL改变的API。

    let history = createBrowserHistory();
    history.listen(({ location, action }) => {
      // this is called whenever new locations come in
      // the action is POP, PUSH, or REPLACE
    });
    
    1
    2
    3
    4
    5

    使用 react-router 时不需操作History对象(Routes 组件会进行操作)

    # 11.2 Location对象

    react-router 对 window.location 进行包装后,提供了一个形式简洁的Location对象,形如:

    {
      pathname: "/bbq/pig-pickins",     // 主机名之后的URL地址
      search: "?campaign=instagram",    // 查询参数
      hash: "#menu",                    // 哈希值,用于确定页面滚动的具体位置
      state: null,                      // 对于 window.history.state 的包装
      key: "aefz24ie"                   // 
    }
    
    1
    2
    3
    4
    5
    6
    7

    # state

    不显示在页面上,不会引起刷新,只由开发人员操作。

    可用于记录用户的跳转详情(从哪跳到当前页面)或在跳转时携带信息。

    可以用在 Link 组件或 navigate 方法中

    <Link to="/pins/123" state={{ fromDashboard: true }} />
    let navigate = useNavigate();
    navigate("/users/123", { state: partialUser });
    
    1
    2
    3

    在目标的组件中,可以用 useLocation 方法获取该对象

    let location = useLocation();
    console.log(location.state);
    
    1
    2

    state中的信息会进行序列化,因此如日期对象等信息会变为string

    # key

    每个Location对象拥有一个唯一的key,可以据此来实现基于Location的滚动管理,或是数据缓存。

    如:将 location.key 和 URL 作为键,每次请求数据前,先查找缓存是否存在来判断是否实际发送请求,来实现客户端数据缓存。

    # 十二、 各类Router组件

    # 12.1 HashRouter和BrowserRouter的区别

    • HashRouter 只会修改URL中的哈希值部分;而 BrowserRouter 修改的是URL本身
    • HashRouter 是纯前端路由,可以通过输入URL直接访问;使用时 BrowserRouter 直接输入URL会显示404,除非配置Nginx将请求指向对应的HTML文件。初次进入 / 路径时或点击 Link 组件跳转时不会发送请求

    # 12.2 unstable_HistoryRouter

    使用 unstable_HistoryRouter 需要传入一个 history 库的实例,这将允许在非react作用于下操作history对象。

    由于项目使用的history和react-router中使用的history版本可能不一样,该API目前标为unstable状态

    # 12.3 MemoryRouter

    HashRouter 和 BrowserRouter 都是依据外部对象(history)进行导航,而 MemoryRouter 则是自己存储和管理状态堆栈,多用于测试场景。

    # 12.4 NativeRouter

    推荐的用于 React Native的Router组件

    # 12.5 StaticRouter

    在nodejs端使用,渲染react应用。

    import * as React from "react";
    import * as ReactDOMServer from "react-dom/server";
    import { StaticRouter } from "react-router-dom/server";
    import http from "http";
    
    function requestHandler(req, res) {
      let html = ReactDOMServer.renderToString(
        <StaticRouter location={req.url}>
          {/* The rest of your app goes here */}
        </StaticRouter>
      );
    
      res.write(html);
      res.end();
    }
    
    http.createServer(requestHandler).listen(3000);
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    # 十三、使用JS对象定义路由(路由表)

    使用 useRoutes hook,可以使用一个JS对象而不是Routes组件与Route组件来定义路由。其功能类似于react-router-config (opens new window)

    useRoutes 的返回是 React Element,或是 null。

    此功能用来管理路由表,相比v5,可能需要借助一些第三方库来实现路由config管理,现在v6版本自带

    # (一) 创建路由表

    
    const routes = useRoutes([
        {
          path: '/profile',
          element: <Profile/>
        },
        {
          path: '/message',
          element: <Message/>
          children: [ // 嵌套路由,需在组件中使用 Outlet组件 进行占位
            {
              path: 'message1',
              element: <Message1/>
            },
            {
              path: 'message2',
              element: <Message2/>
              children:[
                { // 匹配动态路由
              	  path: 'message1/:id',
              	  element: <MessageDetail/>
            	}
              ]
            }
          ]
        },
        {
          path: '/',
          element: <Navigate to='/profile'/>
        },
        {
          path: '/user',
          children: [
            {
              index: true,
              element: <h1>user~</h1> // 这种不属于嵌套路由,这里面children会放到父亲的位置,所以不需要配合Outlet组件使用
            }
          ]
        }
      ])
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40

    对于传入的配置对象, 其类型定义如下:

    interface RouteObject {
        caseSensitive?: boolean;
        children?: RouteObject[];
        element?: React.ReactNode;
        index?: boolean;
        path?: string;
    }
    
    1
    2
    3
    4
    5
    6
    7

    提示

    也可以单独创建 routes 文件夹管理路由表,然后在组件内使用 useRoutes

    # (二) 组件结构内使用函数返回值作为占位符

    return (
    <div>
      <ul className="nav nav-pills">
        {/*路由导航区*/}
        <li role="presentation"><NavLink to="/profile" >Profile</NavLink></li>
        <li role="presentation"><NavLink to="/message">Messages</NavLink></li>
      </ul>
      {/*路由展示区*/}
      <h1>
        {routes}
      </h1>
    </div>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    # (三) 嵌套路由

    在一级路由下面创建二级子路由

    1. 引入路由路由占位符OutLet到组件中
    2. 引入NavLink到组件中改变路径,子路由路径可以不需要加/
    import React from 'react'
    import { NavLink, Outlet } from 'react-router-dom'
    export default function Message() {
      return (
        <div>
          <ul>
            <li>
              <NavLink to='message1'>message1</NavLink>
            </li>
            <li>
              <NavLink to='message2'>message2</NavLink>
            </li>
          </ul>
              {/*路由占位符*/}
          <Outlet/>
        </div>
      )
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

    注意

    不要忘记使用Outlet作为占位符

    # (四) 路由懒加载

    import { lazy } from 'react'
    const Message1 = lazy(() => import('../pages/LoginPage')) // 路由懒加载
    // 在路由表中直接使用即可
    export default [
      {
        path: '/login',
        element: <LoginPage/>
      }
    ]
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    # 十四、其他hooks函数

    直接在组件中调用即可


    useRouterContext

    作用:判断组件是否被路由包裹


    useNavigationType

    作用:判断路由跳转方式

    返回值:push pop replace

    pop是在浏览器中直接打开了这个路由的组件


    useOutlet

    作用:查看组件下级路由信息

    组件若未被挂载则返回null,若以被挂载,则返回该子组件信息


    useResolvePath

    作用:解析路径

    useResolvedPath("https://www.bilibili.com/video/BV1wy4y1D7JT?p=140&spm_id_from=pageDriver")
    
    1
    编辑 (opens new window)
    上次更新: 2023/7/11 18:57:41
    案例演示
    动态路由

    ← 案例演示 动态路由→

    最近更新
    01
    IoC 解决了什么痛点问题?
    03-10
    02
    如何调试 Nest 项目
    03-10
    03
    Provider注入对象
    03-10
    更多文章>
    Copyright © 2019-2025 Study | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式