路由概念
- 核心: 改变
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>