5 路由
约 857 个字 267 行代码 预计阅读时间 6 分钟
SPA(Single Page Application)
- 整个应用只有一个完整的页面
- 点击页面中的链接「不会刷新」页面(只会局部更新)
- 数据需要通过 Ajax 获取,在前端异步展示
所有不存在的资源都返回 index.html
兜底...
Intro
React-Router 在 npm 上共发布了三个不同的版本
react-router
:核心库react-router-dom
:添加了DOM组件,比如 BrowserRouterreact-router-native
:添加了用于 ReactNative 的 API
1 React-Router-Dom@5
- 一般把普通组件放在
components
下,路由组件放在pages
下- 路由组件默认会收到
history, location, match
三个props
Set Up
Basic Usage
import { Link, Route, BrowserRouter } from 'resct-router-dom'
// import 所有要用到的路由组件
export default class App extends Component {
render() {
return (
{/* 由同一对「BrowserRouter」包裹的内容视为独立的路由规则
所以这里干脆把 App 全包上了 */}
<BrowserRouter>
<h1>This is APP!!!</h1>
{/* 编写路由链接,相当于 a 标签 */}
<Link to="/home">Home</Link>
<Link to="/shop">Shop</Link>
{/* 注册路由,相当于 route + router-view */}
<Route path="/home" component={Home}/>
<Route path="/shop" component={Shop}/>
</BrowserRouter>
)
}
}
// 还有另外一种完全包裹法 @ index.js
ReactDOM.render(
<BrowserRouter>
<App/>
</BrowserRouter>,
document.getElementById('root')
)
// 其实用 HashRouter 也行(会多一个'#',认为后面的都是前台资源)
1.1 标签式路由
NavLink 标签
<Link>
标签不支持动态高亮效果
export default class App extends Component {
render() {
return (
<BrowserRouter>
{/* NavLink 会默认为当前选中的 tag 追加 active作为类名
也可以用 activeClassName 进行指定捏 */}
<NavLink activeClassName="demo" to="/home">Home</Link>
<NavLink to="/shop">Shop</Link>
</BrowserRouter>
)
}
}
下面对 NavLink
进行一些小小的封装(一直写一样的 tag 真的很烦诶!)
// MyNavLink 是一般组件,只是封装了 activeClassName & className 部分
// App 中的使用(使用 props 和 slot 传递个性数据)
<MyNavLink to="/home">Home</MyNavLink>
// 组件定义
export default class MyNavLink extends Component {
render() {
return (
{/* 把所有接受的 prop 都塞进 tag 的属性里了 */}
<NavLink {...this.props} activeClassName="active" className="demo">
{this.props.children}
</NavLink>
)
}
}
// 鉴于 NavLink 的标签体内容可以直接用 children 属性配置
<MyNavLink to="/home" children="Home"/>
return (
<NavLink {...this.props} activeClassName="active" className="demo" />
)
Switch 标签
实现 First Match,提高匹配效率
// 用 switch 包裹 route 就可以了
// 默认:Last Match -> /shop 显示 ABC
// Switch:First Match -> /shop 显示 Shop
<Switch>
<Route path="/home" component={Home}/>
<Route path="/shop" component={Shop}/>
<Route path="/shop" component={ABC}/>
</Switch>
样式丢失
刷新后通过 ./index.css
请求资源,cur_path
其实已经不是根目录了,实际返回的是 index.html
下面是三种解决方案:
- 把
index.html
中的link
路径改成/index.css
(去掉最前面的.
) - 把
index.html
中的link
路径改成%PUBLIC_URL%/index.css
(去掉最前面的.
) - 使用
HashRouter
(#
后作为 hash 值,不影响cur_path
)
匹配模式
- 模糊匹配:前缀包含就算匹配上
- 严格匹配:
- 全等匹配,需要配置
<Route exact={true}/>
或者简写<Route exact/>
- 可能会影响嵌套路由的正常显示
- 全等匹配,需要配置
重定向
// 放在路由注册的「最下方」进行兜底 - 全都不符合就跑到 redirect 去
<Switch>
<Route path="/home" component={Home}/>
<Route path="/shop" component={Shop}/>
<Redirect to="/home" />
</Switch>
嵌套路由
开启严格匹配就直接寄了
// 我们在 Home 下进行二级路由,那么就要在 Home.jsx 中进行配置
// NavLink.to && Route.path 中都要带有一级路由 /home
<BrowserRouter>
<NavLink activeClassName="active" to="/home/message">Message</NavLink><br/>
<NavLink activeClassName="active" to="/home/price">Price</NavLink>
{/* 本级别的重定向也要包含上一级路由 */}
<Redirect to="/home/message"/>
<Route path="/home/message" component={hMsg}></Route>
<Route path="/home/price" component={hPrc}></Route>
</BrowserRouter>
路由穿参
// 1. params - 不能直接用模版字符串捏
// 发送
<Link to={`url/${this.state.p1}/${this.state.p2}`}/>
// 接收
// 1. 首先在 Route 中占位
<Route path="/url/:p1/:p2" component={Comp}/>
// 2. 在 Comp 的 props.match.params 中接收
// const {p1, p2} = this.props.match.params
// 2. query/search
// 发送 - 不同参数间使用 & 连接
<Link to={`url/?p1=${this.state.p1}&p2=${this.state.p2}`}/>
// 接收(不需要声明)
// pre:先安装,然后 import qs from 'querystring'
// 1. 初始的 search 是字符串,使用 qs.parse() 转 Obj
// const {search} = this.props.location
// const {p1, p2} = qs.parse(search.slice(1)) => 否则带?
// 3. state 参数(不是组件状态)=> 刷新后保持
// 发送 - 必须把 to 配置为对象
<Link to={{
pathname: '/url',
state: {
s1: 'val1',
s2: 'val2'
}
}}/>
// 在 this.props.location.state 中接收(不需要声明)
// 使用空对象适配清空缓存的情况
// const {s1,s2} = this.props.location.state || {}
针对历史记录的操作
- push(默认)
- replace(在标签中配置)
<Link replace to="url"/>
1.2 编程式路由
路由组件显示还是用
<Route>
配置
// replace 模式
replaceShow = (id, title) => {
// query 参数 - 要用 qs 解析
this.props.history.replace(`url/?id=${id}&t=${title}`)
// params 参数 - Route 要声明接收
this.props.history.replace(`url/${id}/${title}`)
// state 参数 - 第二个参数是 state 对象
this.props.history('url', {
id: 1,
title: 'tt'
})
}
// 绑 click 的时候使用匿名函数调用
<button onClick={()=>{this.replaceShow(id,title)}}/>
// push 模式
this.props.history.push('url')
// 操作浏览历史栈
// 前进 forward
this.props.history.goFoward()
// 后退 back
this.props.history.goBack()
// go - 看参数:+int 前进, -int 后退
this.props.history.go(-3)
withRouter
只有「路由组件」持有
this.props.history
,那么一般组件呢?通过
withRouter
进行加工,使其具备路由组件所持有的 API
import {withRouter} from'react-router-dom'
class Header extends Component {
// 这时候就能用 props.history 下的 API 啦
}
// 注意暴露内容的包裹
export default withRouter(Header)
Browser vs Hash
- 底层原理不同
- BrowserRouter 基于 H5 的 history API 实现,不兼容 IE9 以下版本
- HashRouter 使用 URL 的 hash 值
- path 表现形式不同
- BrowserRouter 路径中不存在
#
- HashRouter 路径包含
#
(后面的参数不会发送给服务器)
- BrowserRouter 路径中不存在
- 刷新对 State 参数的影响
- 对 BrowserRouter 无影响,state 参数存储在 history 对象中
- 会导致 HashRouter 的 state 参数丢失
- HashRouter 可以解决样式丢失的问题捏
2 React-Router 6
一级路由
// 基本解构:使用 xxxRouter 包裹整个路由空间
<BrowserRouter>
// 使用 Link/NavLink 指定跳转 tag
<NavLink to="/home">Home</NavLink>
<NavLink to="/about">About</NavLink>
// 使用 Routes 包裹 route-view,达到 match FIRST
<Routes>
// 配置组件的形式变为 element={<Tag/>}
<Route element={<Home/>}path="/home"/>
<Route element={<About/>}path="/about"/>
// 新的重定向规则,将 / 定向至 /home
// Navigate只要「渲染」就等价于页面跳转,默认为 push 模式
<Route path="/" element={<Navigate to="/home"/>}/>
</Routes>
</BrowserRouter>
- 我们可以配置
<Route caseSensitive>
使得路径严格区分大小写 -
<NavLink>
的动态类名建议使用以下形式对其进行配置:默认情况下会加
.active
一样的话就不用改了
路由表
useRoute
将生成 <Routes>
包裹的全部内容
import {useRoutes} from 'react-router-dom'
// 逐渐 Vue 化
const element = useRoutes([
{
path: '/home',
element: <Home/>
},
{
path: '/about',
element: <About/>
},
{
path: '/',
element: <Navigate to="/home"/>
}
])
// 只要在原来的地方使用 {element} 就行
「坑」
useRoutes() may be used only in the context of a <Router> component.
最后在
index.js
里用<BrowserRouter>
把 App 包起来了
useRoutes()
似乎只能在函数式组件里使用
小小的优化
- 将路由表独立
- 简化 rfc
嵌套路由
逐渐 Vue 化,我们继续改造路由表
首层路由使用
{element}
进行挂在,二级开始使用<Outlet>
占位
// @ router.js
const router = [
{
path: '/home',
element: <Home/>,
children: [
{
path: 'news', // 前面不用加斜杠
element: <News/>
},
{
path: 'message',
element: <Message/>
}
]
}
]
// 不用带父级路由&斜杠,用 /news 就会被当成一级路由
<NavLink to="news">News</NavLink>
// 为了指定路由组件呈现的位置,引入 Outlet(就是 router-view 啦)
<Outlet/>
// 默认情况下会给匹配到的「父级」路由也加上 active
// 如果不希望父级高亮,则配置 <NavLink end/>
路由传参
params
// 发送
<Link to=`news/${id}/${title}`>{title}</Link>
// 需要在路由表中注册
{
path: 'news/:id/:title',
element: <News/>
}
import {useParams} from 'react-router-dom';
const {id, title} = useParams(); // 然后就能用哩
// 有一个更麻烦的东西
const {id, title} = useMatch('/home/nesw/:id:/:title').params;
search
<Link to=`news/?id=${id}&t=${title}`>{title}</Link>
// 不需要占位捏
import {useSearchParams} from 'react-router-dom';
const [search, setSearch] = userSearchParams();
// 最后用 search.get('key') 获取对应参数
// 也可以 setSearch('id=00&title=haha') 重置参数
// 更麻烦的东西
const {id, title} = useLocation().search;
```
#### state
```js
<Link to="news" state={{
id: item.id,
title: item.title
}}>
{title}
</Link>
// 也不需要占位捏
import {useLocation} from 'react-router-dom';
const {state:{id, title}} = useLocation();
编程式路由
import {useNavigate} from 'react-router-dom';
const navigate = useNavigate();
// 去子路由就不用加斜杠了
function goShop() { navigate('/shop', {
replace: false,
// 其他参数只能在路径里拼接
state: {
id: item.id,
title: item.title
}
});}
// 前进后退均通过 navigate(n) 来实现
Hooks
-
useInRouterContext 判断是否组件处在
<Router>
的上下文中被包裹的所有组件(包括后代组件)均返回
true
App 要在
index.js
中包裹才判定有效(在组件 tag 外面) -
useNavigationType 返回当前导航类型
返回值有:POP / PUSH / REPLACE(POP = 直接打开了该组件 / 刷新页面)
-
useOutlet
- 没有挂载当前组件中应当渲染的嵌套路由,返回 null
- 反之,返回展示的嵌套路由对象
-
useResolvedPath 给定 URL,解析 path、search、hash
该 URL 不一定是本项目中的