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>)
}