React Router
react-router:路由核心库,包含诸多和路由功能相关的核心代码
react-router-dom:利用核心库 (它依赖 react-router),结合实际的页面,实现跟页面路由相关的功能
所以我们一般都安装使用 react-router-dom 库
路由的两种模式
我们都知道,url 地址 (如:https://www.suressk.com:443/article?uid=sures&id=1008#hash) 由以下几部分组成:
- 网络协议 (
schema):https(http/https/file等) - 主机名 (
host):www.suressk.com(一般有ip 地址,域名,预设值(如 localhost),局域网中的电脑名等) - 端口号 (
port):443(http 默认 80,https 默认 443) - 访问路径 (
path):/article - 查询参数 (
query/search):?uid=sures&id=1008 - 哈希 (
hash/锚点):#hash
Hash Router
根据 url 地址中的 hash 值来确定显示的组件
因为
hash值变化不会导致页面刷新
它的兼容性比较好,老版浏览器也支持
Browser History Router
HTML5 出现后,新增 History API,从而使浏览器拥有了改变路径而不刷新页面的能力。History 表示浏览器的历史记录,它使用栈的方式存储,当我们每访问一个路径,它会将这个栈中加入一条路径记录
window.history:
-
history.length:获取当前tab页历史记录条数 -
history.pushState(data, title, url):向当前历史记录栈中加入一条新记录data:附加的数据信息title:页面标题 (大部分浏览器不支持)url:新的路径地址
-
history.replaceState(data, title, url):替换历史记录栈当前的历史记录
从而,我们可以根据路径来决定渲染哪个组件
路由组件
react-router 为我们提供了两个重要组件:Router 和 Route 以及一些其他的组件
Router 组件
它本身不做任何展示,仅提供路由模式配置;此组件会产生一个上下文,上下文会提供一些实用的对象和方法,react-router-dom 提供下面两个组件:
-
HashRouter:使用hash 模式匹配 -
BrowserRouter:使用BrowserHistory 模式匹配 -
(这里不讨论)MemoryRouter:将 url 的历史记录保存在内存中 (不读取或写入地址栏),一般用于非浏览器环境(如:React Native)
通常情况下,仅使用一个 Router 组件,而且用它来包裹整个页面
Route 组件
根据不同的地址,展示不同的组件,它有两个重要属性:path 和 component:
-
path:匹配的路径规则 (涉及后面说到的 "动态路径"),它也可以是一个路径正则数组- 默认是不区分大小写的 (可以配合设置
sensitive 属性为 true,使路径匹配区分大小写) - 默认是模糊匹配,只要路径存在,则认为匹配成功 (可以配合设置
exact 属性为 true来精确匹配) - 如果不设置
path,则匹配任意路径
- 默认是不区分大小写的 (可以配合设置
-
component:路径匹配成功后需显示的组件 -
Route的children (子元素):-
传递
React 元素,无论路径是否成功匹配,只要经过对此Route 组件的匹配,那么一定会显示children,且会忽略 Route 组件的 component 属性 -
传递一个函数,该函数有多个参数 (来自 Router 组件产生的上下文),改函数返回 React 元素,则一定会显示返回的元素,且会
忽略 Route 组件的 component 属性
-
-
exact属性:精确匹配 -
sensitive属性:路径匹配区分大小写 -
strict属性:Boolean是否严格匹配路径的最后一个斜杠 (为true时,to="/a",而访问路径为"/a/"则匹配失败)
import React from 'react'
import {BrowserRouter as Router, Route} from 'react-router-dom'
function CompA() {
return <div>CompA</div>
}
function CompB() {
return <div>CompB</div>
}
function CompC() {
return <div>CompC</div>
}
export default function App() {
return (<Router>
<Route path='/a' component={CompA} />
<Route path='/b' component={CompB}>
<div style={{ color: "#f40" }}>Route Children Prop</div>
</Route>
<Route component={CompC} />
</Router>)
}
Route 组件可以写在任意位置,只要保证它是 Router 组件的后代元素即可
withRouter 高阶函数
比如说有这么个使用场景,CompB 组件并不是 Route 组件匹配显示的组件,而是其匹配组件的后代组件,但也需要使用 history,location,match 对象信息:
import React from 'react'
import {BrowserRouter as Router, Route} from 'react-router-dom'
function CompA() {
return <div>
CompA
<CompB />
</div>
}
function CompB() {
/* 这里是拿不到 Route 组件注入的 history, location, match 对象信息的 */
return <div>CompB</div>
}
export default function App() {
return (<Router>
<Route path='/a' component={CompA} />
</Router>)
}
所以,我们需要使用 withRouter 这个高阶函数来包装 CompB 组件,它内部会拿到 Router 组件创建的上下文,并将其作为属性传递给返回的新组件:const RouteCompB = withRouter(CompB),这样在 CompB 组件内部就能使用 history,location,match 对象的信息了
Switch 组件
它会依次进行路径匹配,若路径匹配成功,则不会继续往后匹配 (不加此组件的默认情况下会将 Route 组件的路径全部进行匹配渲染)
由于 Switch 组件会循环所有的子元素 (按照书写 Route 组件 的顺序去依次匹配),若匹配成功,则渲染对应的组件,停止循环;因此,Switch 子组件不能是 Route 和 Redirect 组件以外的其他组件
// 比如,用法如下:
import React, { memo } from 'react'
import {BrowserRouter as Router, Route, Switch} from 'react-router-dom'
function CompA() {
return <div>CompA</div>
}
function CompB() {
return <div>CompB</div>
}
function CompC() {
return <div>CompC</div>
}
function Task() {
return (<Router>
<Switch>
<Route path="/a/b">
<CompB />
</Route>
<Route path="/a" component={CompA}>
<div style={{ color: "#f40" }}>Route Children Prop</div>
</Route>
<Route component={CompC} />
</Switch>
</Router>)
}
export default memo(Task)
这个示例,当我们访问 本地 server 地址 /a/b 路径时,你会发现页面渲染的内容是:CompB,虽然 /a 路径展示 红色 Route Children Prop 也满足路径匹配,但 Switch组件 匹配显示 CompB组件 后就停止了
Link 组件
生成一个无刷新作用的 a 元素,
-
to属性:-
可以是一个字符串:
<Link to="/about" />/<Link to="/courses?sort=name" /> -
可以是一个包含
pathname,search,hash,state四个属性的对象:<Link to={{ pathname: "/courses", search: "?sort=name", hash: "#heading", state: { key: "value" } }} /> -
可以是一个函数,内部返回字符串或对象
-
-
replace属性:Boolean,表明当前Link 组件跳转是否采用替换模式 (默认push方式跳转) -
innerRef属性:用于获得Link组件实际渲染元素的 DOM 元素- 函数:会将这个渲染元素的 DOM 元素作为这个函数的参数
- 对象:通过
React.createRef()创建的 ref 对象
NavLink 组件
它具备 Link 组件 的所有功能,还可以根据渲染元素的链接地址与当前浏览器访问路径进行匹配,为匹配上的元素添加一个 active 类名 (默认匹配上路径的类名)
activeClassName属性:路径匹配成功时的选中类名 (替换默认的.active)activeStyle属性:路径匹配成功的内联样式exact属性:Boolean,是否精确匹配sensitive属性:Boolean,路径匹配是否区分大小写strict属性:Boolean,是否严格匹配路径的最后一个斜杠
Redirect 组件
重定向组件,当加载到该组件时,会无刷新地跳转页面
to属性:重定向的路径 (字符串或对象)push属性:Boolean,默认false(即按replace的模式重定向)from属性:当匹配到from的地址规则时,才进行重定向跳转exact属性:Boolean,from是否精确匹配sensitive属性:Boolean,from路径匹配是否区分大小写strict属性:Boolean,from路径是否严格匹配的最后一个斜杠
路由信息
Router 组件会创建一个 Context,并且会向 Context 中注入一些信息。这个 Context 对开发者是隐藏的,Route 组件匹配到了 path,Route 组件会将这些 Context 中的信息作为属性传递给对应的组件:
history
-
非 window.history对象,我们可以利用该对象进行无刷新跳转等操作 -
push(relativePath, data?):将某个新地址入栈 (历史记录栈),第一个参数为跳转的相对路径;第二个参数即为跳转过去附加的状态数据 (后面可通过props.history.location.state获取,但这个状态数据依赖于跳转,若直接访问这个跳转的路径,状态数据就为空) -
replace(relativePath, data?):将某个新地址替换历史记录栈的当前记录 -
go()/forward()/back():用法与window.history中的同名方法一样
location
-
与
props.history.location是同一个对象,它里面记录了当前地址的相关信息 (search/hash/pathname/state),我们通常会使用第三方库 【query-string】 来解析参数数据 (parse): -
如
location.search:?a=1&b=2&c=3解析为{a: 1, b: 2, c: 3} -
如
location.hash:#d=4&e=5解析为{d: 4, e: 5}
match
-
保存路由匹配的相关信息
-
isExact:指当前路由路径与Route 组件配置的路径是否精确匹配,与Route 组件是否设置exact属性无关 -
params:会根据Route 组件配置的动态参数,将地址栏参数对应位置的数据收集到这个对象中,如:-
<Route path="/news/:year/:month?/:day?" component={News} />,访问路径是:/news/2021/7,那么params = {year: '2021', month: '7', day: undefined} -
<Route path="/news/:year(\d+)/:month?/:day?" component={News} />,加入正则表达式 (年份是数字),若访问路径是:/news/2021/7,那么params = {year: '2021', month: '7', day: undefined};若访问路径是:/news/suressk/7,则此路径匹配失败
-
-
path:匹配上的路由路径使用的路径正则匹配规则 (如上面的示例:"/news/:year(\d+)/:month?/:day?") -
url:实际匹配上的路由路径
react-router 使用了 path-to-regexp 来解析路径正则字符串,将它解析转换成一个真正的正则表达式
通常,向页面传递数据的方式有:
-
使用
state:依赖于手动使用history 对象跳转时传递数据 -
使用
search:在地址栏通过查询参数携带/news?year=2021&month=7&day=21 -
使用
hash:将数据加到地址栏的 hash 值后/news#year=2021&month=7&day=21 -
使用
params:将数据填写到路径中/news/2021/7/21
Hooks
如上面说到 withRouter 高阶函数时,我们需要在某些 Route 匹配到的组件内部使用相关对象或信息,我们就需要使用高阶组件去包一层来通过属性获取,但我们可能只需要使用这其中的某一个对象,那么就可以 在函数组件中 使用以下 Hooks:
useHistory
import { useHistory } from "react-router-dom"
function HomeButton() {
const history = useHistory() // 获取到 history 对象
const handleClick = useCallback(() => {
history.push("/")
}, [history])
return (
<button type="button" onClick={handleClick}>
Go home
</button>
)
}
useLocation
import { useLocation } from "react-router-dom"
function HomeButton() {
const location = useLocation() // 获取到 location 对象
// other code...
return (<>
{/* anything... */}
</>)
}
useParams
import {BrowserRouter as Router, Route, Switch, useParams} from "react-router-dom"
function ArticleDetail(){
const {uid} = useParams() // 获取到路径匹配的 params 对象 => 路径参数 uid
return <div>The article uid is "{uid}"</div>
}
function App() {
// other code...
return (<Router>
<Switch>
<Route path="/" exact component={HomePage} />
<Route path="/article/:uid" exact component={ArticleDetail} />
<Route component={NotFoundPage} />
</Switch>
</Router>)
}