🌙 一、快速开始
🌙 1.配置环境
// 全局安装create-react-app
npm install -g create-react-app
// 启动项目
create-react-app demo-app
cd demo-app
// 启动项目
npm start
// 安装react-router-dom 依赖
npm install react-router-dom --save
2
3
4
5
6
7
8
9
10
🌙 2.简单Demo
See the Pen React-router demo1 by zkkysqs (@zkkysqs) on CodePen.
react-router
是核心部分,是浏览器和原生应用的通用部分 。
react-router-dom
提供了浏览器使用需要的定制组件。
react-router-native
则专门提供了在原生移动应用中需要用到的部分。所以,如果在浏览器开发环境就只需要安装react-router-dom
。本文主要是学习
react-router-dom
部分。
🌙 二、React Router组件
React Router组件主要分为三大类:
- Routers:如
<BrowserRouter>
和<HashRouter>
组件,React Router的父组件,每个React Router组件都是Routers组件。 - Route Matchers :如
<Route>
和<Switch>
组件,我称之为路由出口,渲染UI部分。 - Navigation:如
<Link>
、<NavLink>
和<Redict>
组件,即链接跳转,一般会被渲染为<a>
标签。
🌙 1.Routers路由
🌙 1.1 hash和history两种模式的区别 (opens new window)
Router组件,根据url模式的不同,分为history模式(对应<BrowserRouter>
)和hash模式(对应<HashRouter>
)。
先来理解这两种模式:
对比 | hash模式 (opens new window) | history模式 (opens new window) |
---|---|---|
URL例子 | https://music.163.com/#/friendhttps://pan.baidu.com/disk/home#list/vmode=list | https://www.cnblogs.com/stdzz/p/11703829.html |
特点 | 有特殊标识# | 无 |
对后端的影响 | 与后端配合疏松: hash 虽然出现在 URL 中,不会被包括在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面; hash 模式下,仅 hash 符号之前的内容会被包含在请求中,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。 | 与后端配合紧密:history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 http://www.abc.com/book/id 。如果后端缺少对 /book/id 的路由处理,将返回 404 错误。 |
各自特点 | 1.hash 设置的新值必须与原来不一样才会触发动作将记录添加到栈中。2. hash 只可修改 # 后面的部分,因此只能设置与当前 URL 同文档的 URLhash 3.只可添加短字符串 | 1.history.go(-2); //后退两次 2.history.go(2); //前进两次 3.history.back(); //后退 4.history.forward(); //前进5. history.pushState() (opens new window)6. history.replaceState() (opens new window) |
🌙 1.2 BrowserRouter 组件
使用HTML5 history API更新页面,组件API:
<BrowserRouter
basename={optionalString}
forceRefresh={optionalBool}
getUserConfirmation={optionalFunc}
keyLength={optionalNumber}
>
<Children />
</BrowserRouter>
2
3
4
5
6
7
8
basename:string,为路由添加root url,需要以
/
开头,但是尾部不需要/
:<BrowserRouter basename="/calendar" /> <Link to="/today"/> // renders <a href="/calendar/today">
1
2getUserConfirmation:func,使用window.confirm (opens new window),会出现一个路由离开确认对话框:
<BrowserRouter getUserConfirmation={(message, callback) => { // this is the default behavior const allowTransition = window.confirm(message); callback(allowTransition); }} />
1
2
3
4
5
6
7forcerefresh:bool,如果值为
true
会强制刷新整个页面:<BrowserRouter forceRefresh={true} />
1keyLength:number,
location.key
的长度,默认为6:<BrowserRouter keyLength={12} />
1children:node,需要被渲染的子组件,在react16版本之前,只能有一个子节点,若想包裹多个,需要使用
<div>
包裹。react16版本之后,可以有多个子节点。<BrowserRouter basename={''} forceRefresh={false} getUserConfirmation={() => {}} keyLength={6} > <div> <Children1 /> <Children2 /> </div> </BrowserRouter>
1
2
3
4
5
6
7
8
9
10
11
🌙 1.3 HashRouter组件
使用URL的hash部分来更新页面,组件API:
<HashRouter
basename={optionalString}
getUserConfirmation={optionalFunc}
hashType={optionalString}
>
<App />
</HashRouter>
2
3
4
5
6
7
basename: string,为路由添加root url,需要以
/
开头,但是尾部不需要/
:<HashRouter basename="/calendar"/> <Link to="/today"/> // renders <a href="#/calendar/today">
1
2getuserConfirmation:func,同
<BrowserRouter >
hashType:string,根据值的不同,使
#
在URL位置不同,默认slash
模式:slash
:如#/
和#/sunshine/lollipops
noslash
:如#
和#sunshine/lollipops
hashbang
:如#!/
和#!/sunshine/lollipops
See the Pen React-router HashRouter和BrowserRouter by zkkysqs (@zkkysqs) on CodePen.
🌙 2.Route路由匹配
🌙 1.Route组件
即路由出口,用来将匹配的URL对应的子组件UI渲染出来。
🌙 1.1 Route路由渲染方式
<Route component>
(opens new window)直接关联组件<Route render>
(opens new window)调用render
函数,在路由匹配的时候调用。<Route children>
function (opens new window)调用children
函数,类似于render
函数,但不论路由是否匹配,都会被调用。
以上三种方式,在同一个<Route>
中只能选择一种,如果写了多个,会被覆盖,<Route children>
的优先级更高。
import { BrowserRouter as Router, Route } from "react-router-dom";
function Hello() {
return <h1>Hello World!</h1>;
}
// component方式
ReactDOM.render(
<Router>
<Route path="/hello" component={Hello} />
</Router>,
document.body
);
// render方式
ReactDOM.render(
<Router>
<Route path="/hello" render={() => <Home />} />
</Router>,
document.body
);
// children方式
ReactDOM.render(
<Router>
<Route path="/hello" children={({ match }) => <Home />} />
</Router>,
document.body
);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
🌙 1.2 Route路由匹配规则
path:string | string[] (opens new window)匹配的URL字符串
// 匹配一个 <Route path="/users/:id"> <User /> </Route> // 匹配多个 <Route path={["/users/:id", "/profile/:id"]}> <User /> </Route>
1
2
3
4
5
6
7
8
9exact: bool (opens new window)是否精准匹配还是模糊匹配:
<Route exact path="/one"> <About /> </Route>
1
2
3根据
exact
的值匹配:URL path 目标URL exact的值 匹配与否 /one
/one/two
true
no /one
/one/two
false
yes strict: bool (opens new window)是否开启严格模式,当值为true时,必须后面带斜杠才能匹配:
<Route strict path="/one/"> <About /> </Route>
1
2
3那么可以匹配:
URL path 目标URL 匹配与否 /one/
/one
no /one/
/one/
yes /one/
/one/two
yes 如果必须匹配尾部没有斜杠的,那么
strict
andexact
都得为true
:<Route exact strict path="/one"> <About /> </Route>
1
2
3/one
/one
yes /one
/one/
no /one
/one/two
no sensitive: bool (opens new window)是否区分大小写,当为true时,区分大小写:
<Route sensitive path="/one"> <About /> </Route>
1
2
3URL path 目标URL sensitive值 匹配与否 /one
/one
true
yes /One
/one
true
no /One
/one
false
yes
🌙 2.Switch组件
渲染第一个匹配的URL对应的 <Route>
(opens new window) 或 <Redirect>
(opens new window)
import { Route } from "react-router";
let routes = (
<div>
<Route path="/about">
<About />
</Route>
<Route path="/:user">
<User />
</Route>
<Route>
<NoMatch />
</Route>
</div>
);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
如果URL为/
,只使用<Route>
,那么上面的两个路由都能匹配,会被同时渲染出来,如果加上<Switch>
则只会渲染第一个匹配的URL:
import { Route, Switch } from "react-router";
let routes = (
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/about">
<About />
</Route>
<Route path="/:user">
<User />
</Route>
<Route>
<NoMatch />
</Route>
</Switch>
);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
🌙 3.Navigation路由跳转
🌙 1.Link组件
提供路由跳转的链接,会被渲染为<a>
标签:
<Link to="/about">About</Link>
🌙 1.1 to属性
to: string (opens new window)直接跟URL字符串:
<Link to="/courses?sort=name" />
1to: object (opens new window)对象中包含一系列属性
pathname
: 跳转的路由.search
: url查询参数hash
: url的hash部分state
: State to persist to thelocation
.
<Link to={{ pathname: "/courses", search: "?sort=name", hash: "#the-hash", state: { fromDashboard: true } }} />
1
2
3
4
5
6
7
8to: function (opens new window)以当前的location作为参数,需要返回一个url字符串或者object来匹配to的合法参数。
<Link to={location => ({ ...location, pathname: "/courses" })} /> <Link to={location => `${location.pathname}?sort=name`} />
1
2
🌙 1.2 replace属性
- replace: bool (opens new window)当值为true时,将会替换历史记录的当前URL
<Link to="/courses" replace />
🌙 2.NavLink组件
这是<Link>
组件的升级版,可以给链接加上激活的样式或类名。
🌙 2.1 activeClassName属性
给激活的URL加上类名:
<NavLink to="/faq" activeClassName="selected">
FAQs
</NavLink>
2
3
🌙 2.2 activeStyle属性
activeStyle: object (opens new window)给激活的URL加上样式:
<NavLink to="/faq" activeStyle={{ fontWeight: "bold", color: "red" }} > FAQs </NavLink>
1
2
3
4
5
6
7
8
9
🌙 2.3 exact属性
- exact: bool (opens new window)当为true时,只有精准匹配的时候activeClassName或activeStyle属性才会生效:
<NavLink exact to="/profile">
Profile
</NavLink>
2
3
🌙 2.4 strict属性
- strict: bool (opens new window)当为true时,会严格匹配尾部的斜杠
<NavLink strict to="/events/">
Events
</NavLink>
2
3
🌙 2.5 isActive属性
isActive: func (opens new window)返回布尔值,用来额外判断链接是否是激活的状态:
<NavLink to="/events/123" isActive={(match, location) => { if (!match) { return false; } // only consider an event active if its event id is an odd number const eventID = parseInt(match.params.eventID); return !isNaN(eventID) && eventID % 2 === 1; }} > Event 123 </NavLink>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
🌙 3.Redirect组件
路由重定向到新的URL,会在历史记录中替换掉当前的URL,和HTTP 3xx表现类似:
<Route exact path="/">
{loggedIn ? <Redirect to="/dashboard" /> : <PublicHomePage />}
</Route>
2
3
🌙 3.1 to属性
to: string (opens new window)路由重定向到URL:
<Redirect to="/somewhere/else" />
1-
<Redirect to={{ pathname: "/login", search: "?utm=your+face", state: { referrer: currentLocation } }} />
1
2
3
4
5
6
7
🌙 3.2 push属性
- push: bool (opens new window)若为true则会将重定向路由添加到历史记录中而不是替换:
<Redirect push to="/somewhere/else" />
🌙 3.3 from属性
from: string (opens new window)
<Switch> <Redirect from='/old-path' to='/new-path' /> <Route path='/new-path'> <Place /> </Route> </Switch> // Redirect with matched parameters <Switch> <Redirect from='/users/:id' to='/users/profile/:id'/> <Route path='/users/profile/:id'> <Profile /> </Route> </Switch>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Redirect组件也有strict
、sensitive
和exact
属性,他们和Route组件对应的属性表现一致。
🌙 4.Router Hooks
通过Hooks函数获取路由状态以及路由相关信息,可以实现在组件内部路由跳转。
🌙 4.1 useHistroy
useHistroy提供路由history (opens new window)历史记录,可以用来跳转历史纪录,前进或回退。
🌙 4.2 useLocation
useLocation获取路由location (opens new window)相关信息,如可以获取查询参数:
// location的实例
{
key: 'ac3df4', // not with HashHistory!
pathname: '/somewhere',
search: '?some=search-string',
hash: '#howdy',
state: {
[userDefined]: true
}
}
2
3
4
5
6
7
8
9
10
See the Pen React-router useLocation获取url的location信息 by zkkysqs (@zkkysqs) on CodePen.
🌙 4.3 useParams
useParams获取路由path(路径参数)的一系列key/value值。
See the Pen React-router useParams by zkkysqs (@zkkysqs) on CodePen.
🌙 4.4 useRouteMatch
useRouteMatch像<Route>
方式一样,使用match (opens new window)匹配当前URL,但是可以不用正真渲染<route>
UI,就可以获取匹配到的URL数据。
See the Pen React-router hooks useRouteMatch by zkkysqs (@zkkysqs) on CodePen.
🌙 二、React路由原理
🌙 1、BrowserHistory模式下
🌙 1.1 改变路由
改变路由,指的是通过调用 api 实现的路由跳转,比如开发者在 React 应用中调用 history.push 改变路由,本质上是调用window.history.pushState
方法。
window.history.pushState:
history.pushState(state,title,path)
- 1 state:一个与指定网址相关的状态对象, popstate 事件触发时,该对象会传入回调函数。如果不需要可填 null。
- 2 title:新页面的标题,但是所有浏览器目前都忽略这个值,可填 null 。
- 3 path:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个地址。
window.history.replaceState:
history.replaceState(state,title,path)
参数和 pushState 一样,这个方法会修改当前的 history 对象记录, 但是 history.length 的长度不会改变。
🌙 1.2 监听路由 popstate
window.addEventListener('popstate',function(e){
/* 监听改变 */
})
2
3
同一个文档的 history 对象出现变化时,就会触发 popstate 事件 history.pushState 可以使浏览器地址改变,但是无需刷新页面。注意⚠️的是:用 history.pushState() 或者 history.replaceState() 不会触发 popstate 事件。 popstate 事件只会在浏览器某些行为下触发, 比如点击后退、前进按钮或者调用 history.back()、history.forward()、history.go()方法。
总结: BrowserHistory 模式下的 history 库就是基于上面改变路由,监听路由的方法进行封装处理,最后形成 history 对象,并传递给 Router。
🌙 2.HashHistory模式下
🌙 2.1 改变路由 window.location.hash
通过 window.location.hash 属性获取和设置 hash 值。开发者在哈希路由模式下的应用中,切换路由,本质上是改变 window.location.hash 。
🌙 2.2 监听路由 onhashchange
hash 路由模式下,监听路由变化用的是 hashchange
window.addEventListener('hashchange',function(e){
/* 监听改变 */
})
2
3