S13-05 React-Router 
[TOC]
API 
React-Router 
- <BrowserRouter>:- 返回:,使用 HTML5 的- history API来实现路由功能。
- <HashRouter>:- 返回:,使用- URL hash(即 URL 中的- #字符)来实现路由功能。
- <Switch>:- 返回:,用于渲染与当前 URL 匹配的第一个- <Route>或- <Redirect>组件。当匹配到第一个符合条件的组件时,- <Switch>将停止匹配并呈现该组件。React Router 6.x中被- <Routes>代替
- <Routes>:- 返回:,是一种React Router 6.x新的语法,用于定义路由配置。它是一个包含多个- <Route>或- <React.Fragment>元素的容器组件,每个- <Route>元素都表示一个路由规则。
- <Route>:- 返回:,用于定义路由规则的组件。- 属性
- path:``,表示路由规则的路径。
- element:,表示匹配到该路由规则时渲染的组件。注意:element的值是一个组件实例,如<Home/>
 
- <Link>:- 返回:,用于定义导航链接的组件- 属性
- to:string,表示链接的目标位置
- replace:boolean,表示是否使用替换历史记录而不是添加一个新条目。默认为 false。
- state:object,表示要传递给目标位置的状态。这个状态可以在目标位置的 location 对象中访问到。
 
- <NavLink>:- 返回:,用于定义导航链接的组件,它与- <Link>的功能类似,但是在渲染时会自动添加一个 active 类名,以便高亮当前选中的链接。- 属性
- to:string,表示链接的目标位置
- style:({isActive, isPending?}) => {},设置样式
- className:({isActive, isPending?}) => void,设置class
- activeClassName:string,表示选中链接时要添加的类名。默认为 active。
- activeStyle:object,表示选中链接时要应用的样式
- isActive:function,用于自定义激活链接的逻辑
 
- <Navigate>:- 返回:,用于路由的重定向,当这个组件出现时,就会执行跳转到对应的to路径中- 属性
- to:string,表示要导航到的路径。
- replace:boolean,表示是否使用替换历史记录而不是添加一个新条目。默认为 false。
- state:object,表示要传递给目标位置的状态。这个状态可以在目标位置的 location 对象中访问到。
 
- <Outlet>:- 返回:,是一个新的组件,用于在路由组件中渲染子路由。它充当了一个占位符,用于渲染嵌套路由的内容。
- useNavigate:返回:,
- useParams:返回:,
- useLocation:返回:,
- useRoutes:返回:,
React 
- React.lazy():返回:,
认识React-Router 
认识前端路由 
路由其实是网络工程中的一个术语:
- 在架构一个网络时,非常重要的两个设备就是路由器和交换机。 
- 当然,目前在我们生活中路由器也是越来越被大家所熟知,因为我们生活中都会用到路由器: 
- 事实上,路由器主要维护的是一个映射表; 
- 映射表会决定数据的流向; 
路由的概念在软件工程中出现,最早是在后端路由中实现的,原因是web的发展主要经历了这样一些阶段:
- 后端路由阶段; 
- 前后端分离阶段; 
- 单页面富应用(SPA); 
后端路由阶段 
早期的网站开发整个HTML页面是由服务器来渲染的.
- 服务器直接生产渲染好对应的HTML页面, 返回给客户端进行展示.
但是, 一个网站, 这么多页面服务器如何处理呢?
- 一个页面有自己对应的网址, 也就是URL; 
- URL会发送到服务器, 服务器会通过正则对该URL进行匹配, 并且最后交给一个Controller进行处理; 
- Controller进行各种处理, 最终生成HTML或者数据, 返回给前端. 
上面的这种操作, 就是后端路由:
- 当我们页面中需要请求不同的路径内容时, 交给服务器来进行处理, 服务器渲染好整个页面, 并且将页面返回给客户端. 
- 这种情况下渲染好的页面, 不需要单独加载任何的js和css, 可以直接交给浏览器展示, 这样也有利于SEO的优化. 
后端路由的缺点:
- 一种情况是整个页面的模块由后端人员来编写和维护的; 
- 另一种情况是前端开发人员如果要开发页面, 需要通过PHP和Java等语言来编写页面代码; 
- 而且通常情况下HTML代码和数据以及对应的逻辑会混在一起, 编写和维护都是非常糟糕的事情; 
前后端分离阶段 
前端渲染的理解:
- 每次请求涉及到的静态资源都会从静态资源服务器获取,这些资源包括HTML+CSS+JS,然后在前端对这些请求回来的资源进行渲染; 
- 需要注意的是,客户端的每一次请求,都会从静态资源服务器请求文件; 
- 同时可以看到,和之前的后端路由不同,这时后端只是负责提供API了; 
前后端分离阶段:
- 随着Ajax的出现, 有了前后端分离的开发模式; 
- 后端只提供API来返回数据,前端通过Ajax获取数据,并且可以通过JavaScript将数据渲染到页面中; 
- 这样做最大的优点就是前后端责任的清晰,后端专注于数据上,前端专注于交互和可视化上; 
- 并且当移动端(iOS/Android)出现后,后端不需要进行任何处理,依然使用之前的一套API即可; 
- 目前比较少的网站采用这种模式开发; 
单页面富应用阶段:
- 其实SPA最主要的特点就是在前后端分离的基础上加了一层前端路由. 
- 也就是前端来维护一套路由规则. 
前端路由的核心是什么呢?改变URL,但是页面不进行整体的刷新。
URL的hash 
前端路由是如何做到URL和内容进行映射呢?监听URL的改变。
URL的hash
- URL的hash也就是锚点(#), 本质上是改变window.location的href属性; 
- 我们可以通过直接赋值location.hash来改变href, 但是页面不发生刷新; 


hash的优势就是兼容性更好,在老版IE中都可以运行,但是缺陷是有一个#,显得不像一个真实的路径。
HTML5的History 
history接口是HTML5新增的, 它有六种模式改变URL而不刷新页面:
- replaceState:替换原来的路径; 
- pushState:使用新的路径; 
- popState:路径的回退; 
- go:向前或向后改变路径; 
- forward:向前改变路径; 
- back:向后改变路径; 


认识react-router 
目前前端流行的三大框架, 都有自己的路由实现:
- Angular的ngRouter 
- React的ReactRouter 
- Vue的vue-router 
React Router在最近两年版本更新的较快,并且在最新的React Router6.x版本中发生了较大的变化。
- 目前React Router6.x已经非常稳定,我们可以放心的使用;
Router-基本使用 
安装React Router 
- 安装时,我们选择react-router-dom; 
- react-router会包含一些react-native的内容,web开发并不需要; 
npm install react-router-domRouter的基本使用 
react-router最主要的API是给我们提供的一些组件:
- <BrowserRouter>:- 返回:,使用 HTML5 的- history API来实现路由功能。
- <HashRouter>:- 返回:,使用- URL hash(即 URL 中的- #字符)来实现路由功能。
BrowserRouter或HashRouter
- Router中包含了对路径改变的监听,并且会将相应的路径传递给子组件; 
- BrowserRouter使用history模式; 
- HashRouter使用hash模式; 
// index.js
  import { HashRouter } from 'react-router-dom'
  const root = ReactDOM.createRoot(document.getElementById('root'))
  root.render(
+    <HashRouter>
      <App />
+    </HashRouter>
  )路由映射配置 
- <Switch>:- 返回:,用于渲染与当前 URL 匹配的第一个- <Route>或- <Redirect>组件。当匹配到第一个符合条件的组件时,- <Switch>将停止匹配并呈现该组件。React Router 6.x中被- <Routes>代替
- <Routes>:- 返回:,是一种React Router 6.x新的语法,用于定义路由配置。它是一个包含多个- <Route>或- <React.Fragment>元素的容器组件,每个- <Route>元素都表示一个路由规则。
- <Route>:- 返回:,用于定义路由规则的组件。- 属性
- path:``,表示路由规则的路径。
- element:,表示匹配到该路由规则时渲染的组件。注意:element的值是一个组件实例,如<Home/>
 
Routes:包裹所有的Route,在其中匹配一个路由
- Router5.x使用的是Switch组件
Route:Route用于路径的匹配;
- path属性:用于设置匹配到的路径; 
- element属性:设置匹配到路径后,渲染的组件; - Router5.x使用的是component属性
 
- exact:精准匹配,只有精准匹配到完全一致的路径,才会渲染对应的组件; - Router6.x不再支持该属性
 
App.jsx
export class App extends PureComponent {
  render() {
    return (
      <div>
        <div className="header">Header</div>
        <hr />
        <div className="content">
+          <Routes>
+            <Route path='/home' element={<Home/>}/>
+            <Route path='/profile' element={<Profile/>}/>
+          </Routes>
        </div>
        <hr />
        <div className="footer">Footer</div>
      </div>
    )
  }
}
路由配置和跳转 
- <Link>:- 返回:,用于定义导航链接的组件- 属性
- to:string,表示链接的目标位置
- replace:boolean,表示是否使用替换历史记录而不是添加一个新条目。默认为 false。
- state:object,表示要传递给目标位置的状态。这个状态可以在目标位置的 location 对象中访问到。
 
Link和NavLink:**
- 通常路径的跳转是使用Link组件,最终会被渲染成a元素; 
- NavLink是在Link基础之上增加了一些样式属性(后续学习); 
- to属性:Link中最重要的属性,用于设置跳转到的路径; 


NavLink的使用 
<NavLink>:返回:,用于定义导航链接的组件,它与 <Link> 的功能类似,但是在渲染时会自动添加一个 active 类名,以便高亮当前选中的链接。
- 属性
- to:string,表示链接的目标位置
- style:({isActive, isPending?}) => {},设置样式
- className:({isActive, isPending?}) => void,设置class
- activeClassName:string,表示选中链接时要添加的类名。默认为 active。
- activeStyle:object,表示选中链接时要应用的样式
- isActive:function,用于自定义激活链接的逻辑
需求:路径选中时,对应的a元素变为红色
这个时候,我们要使用NavLink组件来替代Link组件:
- style:传入函数,函数接受一个对象,包含isActive属性 js- <NavLink to="/detail" style={({ isActive }) => ({ backgroundColor: isActive ? '#ddd' : '' })} > 详情 </NavLink>
- className:传入函数,函数接受一个对象,包含isActive属性 js- {/* 1. 普通添加class */} <NavLink to="/category" className={({isActive}) => isActive ? 'mractive' : ''}>分类</NavLink> {/* 2. 使用classnames库 */} <NavLink to="/home" className={({isActive}) => classNames({mractive: isActive})}>首页</NavLink>
- activeClassName - 事实上在默认匹配成功时,NavLink就会添加上一个动态的active class;
 js- <div className="footer"> <NavLink to="/home">首页</NavLink> <NavLink to="/profile">我的</NavLink> </div> - 所以我们也可以直接编写样式
 
当然,如果你担心这个class在其他地方被使用了,出现样式的层叠,也可以自定义class
Navigate导航 
<Navigate>:返回:,用于路由的重定向,当这个组件出现时,就会执行跳转到对应的to路径中
- 属性
- to:string,表示要导航到的路径。
- replace:boolean,表示是否使用替换历史记录而不是添加一个新条目。默认为 false。
- state:object,表示要传递给目标位置的状态。这个状态可以在目标位置的 location 对象中访问到。
Navigate用于路由的重定向,当这个组件出现时,就会执行跳转到对应的to路径中:
我们这里使用这样的一个案例:
- 用户跳转到Profile界面; 
- 但是在Profile界面有一个isLogin用于记录用户是否登录: - true:那么显示用户的名称; 
- false:直接重定向到登录界面; 
 


示例:点击登录跳转到Home页
export class Profile extends PureComponent {
  constructor() {
    super()
    this.state = {
+      isLogin: false
    }
  }
  render() {
    const { isLogin } = this.state
    return (
      <div>
        <h3>Profile</h3>
        {/* 
          功能:通过isLogin判断是否登录,
            如果登录则跳转到首页,
            如果没有登录则留在本页,并且在本页提供一个登录按钮,点击实现登录
        */}
        {
+          isLogin ? <Navigate to='/home'/> : <button onClick={e => this.setState({isLogin: true})}>登录</button>
        }
      </div>
    )
  }
}示例:匹配到/时直接跳转到/home页面
  <Routes>
+    <Route path='/' element={<Navigate to='/home'/>} />
    <Route path="/home" element={<Home />} />
    <Route path="/profile" element={<Profile />} />
    <Route path="/detail" element={<Detail />} />
    <Route path="/category" element={<Category />} />
  </Routes>Not Found页面配置 
如果用户随意输入一个地址,该地址无法匹配,那么在路由匹配的位置将什么内容都不显示。
很多时候,我们希望在这种情况下,让用户看到一个Not Found的页面。
这个过程非常简单:
- 开发一个Not Found页面; 
- 配置对应的Route,并且*设置path为**即可; 
  <Routes>
    <Route path='/' element={<Navigate to='/home'/>} />
    <Route path="/home" element={<Home />} />
    <Route path="/profile" element={<Profile />} />
    <Route path="/detail" element={<Detail />} />
    <Route path="/category" element={<Category />} />
+    <Route path='*' element={<NotFound />} />
  </Routes>
Router-路由嵌套 
- <Outlet>:- 返回:,是一个新的组件,用于在路由组件中渲染子路由。它充当了一个占位符,用于渲染嵌套路由的内容。
在开发中,路由之间是存在嵌套关系的。
这里我们假设Home页面中有两个页面内容:
- 推荐列表和排行榜列表; 
- 点击不同的链接可以跳转到不同的地方,显示不同的内容; 
Outlet 组件用于在父路由元素中作为子路由的占位元素。
App.jsx
  <Routes>
    <Route path='/' element={<Navigate to='/home'/>} />
+    <Route path="/home" element={<Home />}>
+      <Route path='/home/recommend' element={<HomeRecommend />}/>
+      <Route path='/home/rank' element={<HomeRank />}/>
+    </Route>
    <Route path="/profile" element={<Profile />} />
    <Route path="/detail" element={<Detail />} />
    <Route path="/category" element={<Category />} />
    <Route path='*' element={<NotFound />} />
  </Routes>Home.jsx
  <Link to='/home/recommend'>推荐榜 </Link>
  <Link to='/home/rank'>排行榜</Link>
+  <Outlet />
Router-手动路由跳转 
目前我们实现的跳转主要是通过Link或者NavLink进行跳转的,实际上我们也可以通过JavaScript代码进行跳转。
- 我们知道Navigate组件是可以进行路由跳转的,但是依然是组件的方式。 
- 如果我们希望通过JavaScript代码逻辑进行跳转(比如点击了一个button),那么就需要获取到navigate对象。 
在Router6.x版本之后,代码类的API都迁移到了hooks的写法:
- 如果我们希望进行代码跳转,需要通过useNavigate的Hook获取到navigate对象进行操作; 
- 那么如果是一个函数式组件,我们可以直接调用,但是如果是一个类组件呢? 

使用useNavigate的方法:
1、修改成函数组件
  import React from 'react'
  import { Navigate, Route, Routes, useNavigate } from 'react-router-dom'
  import Home from './cpns/Home'
  import Profile from './cpns/Profile'
  import NotFound from './cpns/NotFound'
+  export function App(props) {
    // 1. 调用useNavigate钩子,返回方法
+    const navigate = useNavigate()
    return (
      <div>
        <div className="header">
          {/* 2. 使用navigate方法,实现跳转 */}
+          <button onClick={e => navigate('/home')}>首页</button>
+          <button onClick={e => navigate('/profile')}>我的</button>
        </div>
        <hr />
        <div className="content">
          <Routes>
            <Route path="/" element={<Navigate to="/home" />} />
            <Route path="/home" element={<Home />} />
            <Route path="/profile" element={<Profile />} />
            <Route path="*" element={<NotFound />} />
          </Routes>
        </div>
        <hr />
        <div className="footer">Footer</div>
      </div>
    )
  }
  export default App2、对类组件使用高阶组件包裹
hoc/withRouter.js
  import { useNavigate } from "react-router-dom"
  function withRouter(Cpn) {
    return function(props) {
      const navigate = useNavigate()
      const router = { navigate }
      return <Cpn {...props} router={router}/>
    }
  }
  export default withRouterApp.jsx
import React, { PureComponent } from 'react'
import { Navigate, Route, Routes } from 'react-router-dom'
import Home from './cpns/Home'
import Profile from './cpns/Profile'
import NotFound from './cpns/NotFound'
import withRouter from './hoc/withRouter'
+ export class App extends PureComponent {
  render() {
    // 2. 解构出router对象中的navigate方法
+    const { router } = this.props
+    const { navigate } = router
    return (
      <div>
        <div className="header">
          {/* 3. 使用navigate方法,实现跳转 */}
+          <button onClick={(e) => navigate('/home')}>首页</button>
+          <button onClick={(e) => navigate('/profile')}>我的</button>
        </div>
        <hr />
        <div className="content">
          <Routes>
            <Route path="/" element={<Navigate to="/home" />} />
            <Route path="/home" element={<Home />} />
            <Route path="/profile" element={<Profile />} />
            <Route path="*" element={<NotFound />} />
          </Routes>
        </div>
        <hr />
        <div className="footer">Footer</div>
      </div>
    )
  }
}
  // 1. 使用高阶组件withRouter包裹App
+ export default withRouter(App)Router-参数传递 
传递参数有二种方式:
- 动态路由参数 
- 查询字符串参数 
1、动态路由参数
动态路由的概念指的是路由中的路径并不会固定:
- 比如/detail的path对应一个组件Detail; 
- 如果我们将path在Route匹配时写成/detail/:id,那么 /detail/abc、/detail/123都可以匹配到该Route,并且进行显示; 
- 这个匹配规则,我们就称之为动态路由; 
- 通常情况下,使用动态路由可以为路由传递参数。 
跳转时传递参数
1、路由
  <Routes>
    <Route path="/" element={<Navigate to="/home" />} />
    {/* 1. 路由传参-动态路由 */}
+    <Route path="/home/:id" element={<Home />} />
    <Route path="/profile" element={<Profile />} />
    <Route path="*" element={<NotFound />} />
  </Routes>2、跳转时传参
  {/* 2. 使用navigate方法,实现跳转 */}
+  <button onClick={(e) => navigate('/home/123')}>首页</button>
+  <Link to="/profile/456">我的</Link>3、由于类组件不能使用useParams,所以必须对该类组件进行增强
  import { useNavigate, useParams } from "react-router-dom"
  function withRouter(Cpn) {
    return function(props) {
      // 路由跳转
      const navigate = useNavigate()
      // 路由传参-动态路由
+      const params = useParams()
      const router = { navigate, params }
      return <Cpn {...props} router={router}/>
    }
  }
  export default withRouter4、跳转后获取参数
import React, { PureComponent } from 'react'
import withRouter from '../hoc/withRouter'
export class Home extends PureComponent {
  render() {
    const { router } = this.props
+    const { params } = router
    console.log('Home: ', params)
    return (
      <div>
        <h3>Home</h3>
+        <div>id: {params.id}</div>
      </div>
    )
  }
}
+ export default withRouter(Home)2、查询字符串参数
- useLocation:``,
- useSearchParams:``,
1、路由
  <Routes>
    <Route path="/" element={<Navigate to="/home" />} />
    {/* 1. 路由传参-动态路由 */}
    <Route path="/home/:id" element={<Home />} />
    <Route path="/profile" element={<Profile />} />
    {/* 2. 路由传参-查询字符串 */}
+    <Route path='/about' element={<About />}/>
    <Route path="*" element={<NotFound />} />
  </Routes>2、传递参数
<Link to="/about?name=Tom&age=20">关于</Link>3、由于类组件不能使用useParams,所以必须对该类组件进行增强
方式一:useSearchParams()
方式二:useLocation()
  import { useLocation, useNavigate, useParams, useSearchParams } from "react-router-dom"
  function withRouter(Cpn) {
    return function(props) {
      // 路由跳转
      const navigate = useNavigate()
      // 路由传参-动态路由
      const params = useParams()
      // 路由传参-查询字符串-useSearchParams
+      const [ searchParams ] = useSearchParams()
+      const query = Object.fromEntries(searchParams.entries())
      
      // 路由传参-查询字符串-useLocation
+      const { search } = useLocation()
+      const searchEntries = new URLSearchParams(search)
+      const searchObj = Object.fromEntries(searchEntries)
      const router = { navigate, params, query, searchObj }
      return <Cpn {...props} router={router}/>
    }
  }
  export default withRouter4、获取参数
import React, { PureComponent } from 'react'
import withRouter from '../hoc/withRouter'
export class About extends PureComponent {
  render() {
    const { router } = this.props
+    const { query, searchObj } = router
    return (
      <div>
        <div>About</div>
+        <div>name: {query.name}, age: {query.age}</div>
+        <div>name-{searchObj.name}, age-{searchObj.age}</div>
      </div>
    )
  }
}
+ export default withRouter(About)Router-配置文件 
目前我们所有的路由定义都是直接使用Route组件,并且添加属性来完成的。
但是这样的方式会让路由变得非常混乱,我们希望将所有的路由配置放到一个地方进行集中管理:
- 在早期的时候,Router并且没有提供相关的API,我们需要借助于 - react-router-config完成;
- 在Router6.x中,为我们提供了useRoutes API可以完成相关的配置; 
配置过程 
1、创建 router/index.js
通过 useRoutes 将配置信息生成为Route组件,导出一个BaseRoute 的函数组件
import { Navigate, useRoutes } from "react-router-dom";
export default function BaseRoute() {
  return useRoutes(
    [
      { path: '/', element: <Navigate to='/home'/> },
      {
        path: '/home', 
        element: <Home />, 
        children: [
          { path: '/home/recommend', element: <HomeRecommend /> },
          { path: '/home/banner', element: <HomeBanner /> }
        ]
      },
      { path: '/profile', element: <Profile /> },
      { path: '/about', element: <About /> },
      { path: '*', element: <NotFound /> }
    ]
  )
}2、在App.jsx 中使用该组件
  <div className="content">
+    <BaseRoute />
  </div>注意: 这里不能在router/index.js 中导出routes ,然后在App.jsx 中使用useRoutes 生成Route组件,会报错
React Hook "useRoutes" cannot be called in a class component注意: 在路由配置中如果该路由下面有子路由(children),那么path不能写成/home/:id这样,只能是/home
3、如果我们对某些组件进行了异步加载(懒加载),那么需要使用Suspense进行包裹,实现分包


懒加载 
React.lazy() 是 React 16.6 新增的 API,它实现了一种动态加载组件的方式,可以延迟组件的加载到真正需要渲染的时候再进行加载,从而优化页面加载速度。其语法如下:
const MyComponent = React.lazy(() => import('./MyComponent'));其中 import() 函数返回一个 Promise 对象,表示 MyComponent 组件的代码将在被第一次渲染时动态地加载。
在使用React.lazy()时,需要注意几个方面:
- React.lazy()只能用于函数式组件,不能用于 class 组件;
- 必须使用 React.Suspense 组件来包装懒加载组件,以防止在加载完成之前渲染出错;
- 在开发环境中,React.lazy() 需要与 babel 插件*@babel/plugin-syntax-dynamic-import* 配合使用。
以下是一个示例,展示了如何使用React.lazy()实现懒加载:
import React, { lazy, Suspense } from 'react';
const OtherComponent = lazy(() => import('./OtherComponent'));
function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
      </Suspense>
    </div>
  );
}在上面的代码中,我们使用 React.lazy() 动态加载了一个组件,然后使用 <Suspense> 组件包裹起来,设置了一个 fallback 属性,表示在组件加载完成前显示的内容,这里设置为 "Loading..."。这样确保了即使组件还未加载完成,也不会影响整个页面的渲染。
另外值得一提的是,React.lazy()并不会影响应用的事件处理逻辑,因为它并不是异步地获取数据,而是关于代码的懒加载。