路由概念
- 核心: 改变 
URL,但是页面不进行整体的刷新 - 作用 :维护 
URL和渲染页面的映射关系 - 目的: 通过 JavaScript 监听 
URL的改变,并且根据URL的不同重新渲染页面 
改变
URL但不刷新页面的两种方式
- 通过 
URL的hash改变URL - 通过 
HTML5的history模式改变URL 
Hash模式
- 使用 
URL的hash来模拟一个完整的URL,当hash值改变时,页面不会重新加载 URL的hash也就是锚点(#),本质上是改变window.location的hash属性hash模式的原理是通过监听浏览器的hashchange事件,根据不同的值渲染不同的内容
            
            
              html
              
              
            
          
           <a href="#/home">home</a>
 <a href="#/user">user</a>
 
 <h1 class="router-view">Default</h1>
        
            
            
              javascript
              
              
            
          
           const routerViewEl = document.querySelector('.router-view')
 window.addEventListener('hashchange', function () {
   switch (location.hash) {
     case '#/home':
       routerViewEl.innerHTML = 'Home';
       break
     case '#/user':
       routerViewEl.innerHTML = 'User';
       break
     default:
       routerViewEl.innerHTML = 'Default';
   }
 })
        hash的优势就是兼容性更好,在老版IE中都可以运行,但缺陷是有#,显得不像真实路径
history模式
history接口是HTML5新增的,提供了对历史记录修改的功能
六种改变
URL而不刷新页面的模式
replaceState: 替换原来的路径(不保留历史记录)pushState: 追加新的路径(类似入栈)popState: 路径回退(类似出栈)go: 向前或向后改变路径forward: 向前改变路径back: 向后改变路径
            
            
              html
              
              
            
          
             <a href="/home">home</a>
   <a href="/user">user</a>
   
   <h1 class="router-view">Default</h1>
        
            
            
              javascript
              
              
            
          
           function historyChange() {
   switch (location.pathname) {
     case '/home':
       routerViewEl.innerHTML = 'Home';
       break
     case '/user':
       routerViewEl.innerHTML = 'User';
       break
     default:
       routerViewEl.innerHTML = 'Default';
   }
 }
 
 window.addEventListener('popstate', historyChange) // 监听点击浏览器的前进后退按钮
 
 const routerViewEl = document.querySelector('.router-view')
 const linkEls = document.getElementsByTagName('a')
 for (const linkEl of linkEls) {
   linkEl.addEventListener('click', function (e) {
     e.preventDefault()
     const hrefValue = this.getAttribute('href')
     history.pushState({}, '', hrefValue) // 改变url
     historyChange()
   })
 }
        - 监听浏览器的 
popstate事件 ,但调用history.pushState()或history.replaceState()并不会使其触发, 只有点击浏览器的回退或前进按钮才会触发该事件 
基本使用
- 选择 
react-router-dom,因为react-router会包含react-native的内容,web开发并不需要 
            
            
              shell
              
              
            
          
           npm i react-router-dom
        
react-router-dom最主要的API是提供的组件
- 
BrowserRouter组件: 使用
history模式 - 
HashRouter组件: 使用
hash模式 - 
Router中包含对路径改变的监听,并且会将相应的路径传递给子组件 
            
            
              jsx
              
              
            
          
           import { HashRouter } from 'react-router-dom';
 
 <HashRouter>
   <App />
 </HashRouter>
        映射配置
- Routes组件: 包裹所有的 
Route,在其中配置路由的映射关系(Router5.x使用的是Switch组件) - Route组件: 
Route用于路径的匹配 
Route组件的常用属性
- path: 用于设置匹配到的路径
 - element: 匹配到对应路径后需要渲染的组件(
Router5.x使用的是component属性) - exact: 精准匹配,只有精准匹配到完全一致的路径才渲染对应组件(
Router6.x不再支持该属性) 
            
            
              jsx
              
              
            
          
           import { Route, Routes } from 'react-router-dom';
 
 {/* 映射关系:path => Component */}
 <div className="content">
   {/* 映射关系:path => Component */}
   <Routes>
     <Route path='/' element={<Home/>} />
     <Route path='/home' element={<Home/>} />
     <Route path='/about' element={<About/>} />
   </Routes>
 </div>
        跳转配置
- Link: 通常路径的跳转是使用 
Link组件,最终会被渲染成a元素 
Link组件的常用属性
- to: 
Link中最重要的属性,用于设置跳转的目标路径 - replace: 使用目标路径直接替换当前路径
 - reloadDocument: 是否重新加载页面,默认 
false 
            
            
              jsx
              
              
            
          
           import { Link } from 'react-router-dom';
 
 <div className="link-box">
   <Link to="/home">Home</Link>
   <Link to="/about">About</Link>
 </div>
        - NavLink: 在 
Link基础之上增加了一些样式属性 
NavLink组件的常用属性
- style: 传入函数,函数有一个为对象的参数,其中包含 
isActive属性 - className: 和 
style属性同理 
            
            
              jsx
              
              
            
          
           import { NavLink } from 'react-router-dom';
 
  const getActiveStyle = ({isActive}) => {
   return { color: isActive ? 'red' : '' }
 };
 
 const getActiveClass = ({isActive}) => {
   return isActive ? 'link-active' : ''
 };
 
 <div className="link-box">
   <NavLink to="/home" style={this.getActiveStyle}>Home</NavLink>
   <NavLink to="/about" className={this.getActiveClass}>About</NavLink>
 </div>
        NavLink也可以对激活时添加的active类自定义样式

            
            
              css
              
              
            
          
           .link-box {
   .active {
     color: red;
     font-size: 25px;
   }
 }
        
Navigate导航
Navigate用于路由的重定向,使用该组件就会跳转到对应的to路径中(Router5.x使用的是Redirect)
            
            
              jsx
              
              
            
          
           import { Navigate } from 'react-router-dom';
 
 <Navigate to='/home'/>
        - 当用户第一次访问页面时,可以使用 
Navigate组件作重定向操作 
            
            
              jsx
              
              
            
          
           import { Routes, Route, Navigate } from 'react-router-dom';
 
 <Routes>
   <Route path='/' element={<Navigate to='/home' />}/>
   <Route path='/home' element={<Home/>} />
 </Routes>
        Not Found配置
- 当访问不存在的 
URL时,使用*通配符匹配一个404兜底路由 
            
            
              jsx
              
              
            
          
           <Route path='*' element={<NotFound/>} />
        
路由嵌套
- 在开发中路由之间是存在嵌套关系的,可以使用 
<Route>组件以嵌套的形式配置 
            
            
              jsx
              
              
            
          
           import { Routes, Route, Navigate } from 'react-router-dom';
 <Routes>
   <Route path='/home' element={<Home/>} >
     {/* 配置子路由 */}
     <Route path='/home' element={<Navigate to='/home/recommend' />} />
     <Route path='/home/recommend' element={<HomeRecommend/>} />
     <Route path='/home/ranking' element={<HomeRanking/>} />
   </Route>
 </Routes>
        - 使用 
<Outlet>组件,用于在父路由元素中作为子路由的占位元素 
            
            
              jsx
              
              
            
          
           // Home.jsx
 import { NavLink, Outlet } from 'react-router-dom';
 
 <div>
   <h1>Home Page</h1>
   <div className='home-nav'>
     <NavLink to="/home/recommend">推荐</NavLink>
     <NavLink to="/home/ranking">排行榜</NavLink>
   </div>
   {/* 占位 */}
   <Outlet/>
 </div>
        
编程式路由
- 路由可以通过 
<Link>或者<NavLink>进行跳转,也可以通过JavaScript代码进行跳转 - 在 
Router6.x版本之后,编程式路由的API都迁移到了Hooks的写法 - 可以通过 
react-router-dom提供的useNavigate的Hook获取到navigate函数进行操作 
            
            
              jsx
              
              
            
          
          const App = memo((props) => {
  const navigate = useNavigate()
  return (
  	<AppWrapper>
  	  <button onClick={e => navigate('/category')}>分类</button>
  	  <button onClick={e => navigate('/order')}>订单</button>
  	</AppWrapper>
  )
})
        - 注意: 
Router6.x版本提供的Hook只能在函数式组件中使用,类组件中则无法使用 - 如果想要在类组件中使用 
Hook,可以通过高阶组件进行封装,将hook传到组件的props上 
            
            
              jsx
              
              
            
          
           import { useNavigate } from 'react-router-dom';
 
 export default function withRouter(Component) {
   return function(props) {
     const navigate = useNavigate();
     return <Component {...props} router={{navigate}}/>
   }
 }
        
            
            
              jsx
              
              
            
          
           import { useNavigate } from 'react-router-dom';
 import withRouter from '../hoc/withRouter';
 
 class Home extends PureComponent {
   render() {
     const { navigate } = this.props.router;
     return <button onClick = {e => navigate('/home/songMenu')}>歌单</button>
   }
 }
 
 export default withRouter(Home)
        
navigate函数可以接收三个参数
- to: 跳转的目标路径(必传)
 - options: 对象,
{replace: boolean, state: any},使用replace可更改跳转方式 - delta: 步数,如 
navigate(1)前进一步 
参数传递
React中通过路由传递参数有两种方式
- 通过动态路由的方式(params)
 - 通过查询字符串传递参数(query)
 
动态路由(params)
- 动态路由概念: 路由中的路径并不固定,如 
/detail/:id,那么/detail/abc、/detail/123都可以匹配到该Route,这种匹配规则称为动态路由 - 通常情况下,使用动态路由可以为路由传递参数
 
            
            
              jsx
              
              
            
          
           <Route path='/detail/:id' element={<Detail/>} />
        - 在点击跳转时可以在路径后携带
 
            
            
              jsx
              
              
            
          
           import { useNavigate } from 'react-router-dom';
 const navigate = useNavigate();
 
 <button onClick={e => navigate(`/detail/${id}`)}>详情</button>
        - 获取方式则是通过 
useParams这个hook 
            
            
              jsx
              
              
            
          
           import { useParams } from 'react-router-dom';
 const params = useParams();
 
 <h2>id:{params.id}</h2>
        查询字符串(query)
- 通过查询字符串传参,则是在跳转对应 
URL拼接参数,如/detail?id=111&name=tony,路由配置仍然是固定写法 
            
            
              jsx
              
              
            
          
           <Route path='/detail' element={<Detail/>} />
        - 在点击跳转时可以在路径后拼接需要传递的参数
 
            
            
              jsx
              
              
            
          
           import { useNavigate } from 'react-router-dom';
 const navigate = useNavigate();
 
 <button onClick={e => navigate(`/detail/?id=${id}`)}>详情</button>
        - 可以通过 
useSearchParams这个hook获取 
useSearchParams返回一个数组,数组中携带两个元素
- searchParams: 携带路由参数的键值对列表
 - setSearchParams: 修改路由参数的方法
 
            
            
              jsx
              
              
            
          
           const [ searchParams, setSearchParams ] = useSearchParams();
 const query = Object.fromEntries(searchParams); // 转化为普通对象
 
 <h2>id:{query.id}</h2>
        - 也可以通过 
useLocation这个hook获取,但获取到的形式为"?id=111",需要自行处理 
            
            
              jsx
              
              
            
          
           const { search } = useLocation(); // 返回一个location对象,路由传递的参数在search属性中
 const query = { id: search.split('=').pop() }; // 处理参数
 
 <h2>id:{query.id}</h2>
        配置文件
- 如果将路由的映射配置都使用 
<Route>写在组件内部,那么路由会变得非常混乱 - 可以使用 
routes映射配置文件,结合useRoutes进行渲染,但早期的Router并没有提供相关的API,需要借助于react-router-config完成 - 路由映射文件如下,类似 
VueRouter的配置 
            
            
              jsx
              
              
            
          
           const routes = [
   {
     path: '/',
     element: <Navigate to="home"/>
   },
   {
     path: '/home',
     element: <Home/>,
     children: [
       {
         path: '/home',
         element: <Navigate to="/home/recommend"/>
       },
       {
         path: '/home/recommend',
         element: <HomeRecommend />
       },
       // ...
     ]
   }
   // ...
   {
     path: '*',
     element: <Category />,
   }
 ]
 
 export default routes;
        - 使用 
useRoutes这个Hook渲染 
            
            
              jsx
              
              
            
          
           import { NavLink, useNavigate, useRoutes } from 'react-router-dom';
 import routes from './router';
 
 <div className="content">
   { useRoutes(routes) }
 </div>
        路由懒加载
React中使用路由懒加载,需要使用React.lazy()方法和Suspense组件结合- 定义需要懒加载的路由
 
            
            
              javascript
              
              
            
          
           const Home = React.lazy(() => import('../pages/Home'))
 const HomeRecommend = React.lazy(() => import('../components/HomeRecommend'))
 const About = React.lazy(() => import('../pages/About'))
 const Login = React.lazy(() => import('../pages/Login'))
        - 使用 
Suspense组件对App根组件进行包裹,并且添加fallback属性,该属性中的内容是路由组件还没完成加载时所显示的内容 
            
            
              jsx
              
              
            
          
           import { Suspense } from 'react';
 import { HashRouter } from 'react-router-dom';
 
 <HashRouter>
   <Suspense fallback={<h1>Loading</h1>}>
     <App />
   </Suspense>
 </HashRouter>