react-Router 的使用和配置

路由组件和一般组件

  1. 写法不同
    一般组件:<Demo>
    路由组件:<Route path=’/demo’ component={Demo}>
  2. 存放位置不同
    一般组件:src/component
    路由组件:src/pages
  3. 接受到的 props 不同
    一般组件:写组件标签时传递了什么,就能接收到什么
    路由组件:接收到三个固定的 props:history、location、match
  4. 本文会讲到的 react-router API:
  • history:

    • action: “PUSH”
    • block: function block(prompt)​​
    • createHref: function createHref(location)​​
    • [√] go: function go(n)​​
    • [√] goBack: function goBack()​​
    • [√] goForward: function goForward()
    • length: 3
    • listen: function listen(listener)​​
    • location: Object { pathname: “/home”, search: “”, key: “m9oksj”, … }​
    • [√] push: function push(path, state)​​
    • [√] replace: function replace(path, state)​​
  • location:

    • hash: “”
    • key: “rj45aa”
    • [√] pathname: “/about”
    • [√] search: “”
    • [√] state: undefined
  • match:

    • isExact: true​​ // 精准匹配
    • [√] params: Object { }
    • [√] path: “/about”​​
    • [√] url: “/about”​​

路由组件 BrowerRouter 和 HashRouter

react-router 的工作方式:在组件树顶层放一个 Router 组件,然后在组件树中散落着很多 Route 组件,顶层的 Router 组件负责分析监听 URL 的变化,在它之下的 Route 组件可以直接读取这些信息

  1. 底层原理不一样:

    • BrowserRouter 使用的是 H5 的 history API,不兼容 IE9 以下版本。
    • HashRouter 使用的是 URL 的哈希值(锚点),# 的英文为 hash。
    • MemoryHistory:常用于非 DOM 环境,例如 React Native 或者测试,History 存于内存
  2. url 表现形式不一样

  3. 刷新后对路由 state 参数的影响

    • 对 BrowserRouter 没有任何影响,因为 state 保存在 history 对象中
    • 对 HashRouter 刷新后会导致路由 state 参数的丢失
  4. 何时选用

    • 对于浏览器项目我们通常选用: <BrowserRouter> 和 <HashRouter> 组件来实现 Router
    • react-native 项目我们通常选用:MemoryHistory
  5. 备注:HashRouter 可以用于解决一些路径错误相关的问题

react-router-dom 的基本使用

react-router-dom 的安装

1
npm i react-router-dom

基本使用的示例代码

src 下目录如下

  • src
    • pages(用于存放路由组件)
      • About
        • index.jsx
      • Home
        • index.jsx
    • App.jsx
    • index.js

src/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/index.js
// 1、引入App.jsx和模块

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
/*
页面只能使用一个路由器进行管理,所以在最外层(App层)包裹一层router
*/
import { BrowserRouter as Router } from 'react-router-dom'

// 2、渲染App组件

ReactDOM.render(
<Router>
<App />
</Router>,
document.getElementById('root')
)

src/App.jsx

1
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
// src/App.jsx:
// 1、引入模块和路由组件

import React, { Component } from 'react'
import { Route, Link } from 'react-router-dom'
import Home from './pages/Home/index.jsx'
import About from './pages/About/index.jsx'

// 2、声明路由

/*
原生html中,靠<a>跳转到不同的页面
<a class="list-group-item active" href="./about.html">About</a>
<a class="list-group-item" href="./home.html">Home</a>
*/

/*
编写路由连接,在react中靠路由链接Link实现切换组件,整个页面需要用一个路由器管理,
所以Link、NavLink、Route组件都需要用Router组件包裹,这里的Router写在了index.js中,包裹在最外层
*/

<Link className="list-group-item" to="/about">About</Link>
<Link className="list-group-item" to="/home">Home</Link>

// 3、注册路由

<Route exact path="/" component={About}></Route>
<Route path="/about" component={About}></Route>
<Route path="/home" component={Home}></Route>

src/pages/Home/index.js

1
2
3
4
5
6
7
import React, { Component } from 'react'

export default class Home extends Component {
render() {
return <h3>我是Home的内容</h3>
}
}

src/pages/About/index.js

1
2
3
4
5
6
7
import React, { Component } from 'react'

export default class About extends Component {
render() {
return <h3>我是About的内容</h3>
}
}

Link 和 NavLink 都可以用来指定路由跳转,NavLink 属性更多

方法一:通过字符串执行跳转路由

1
2
3
4
5
6
7
8
9
10
11
12
13

// 1、引入组件和模块
import Home from './pages/Home/index.jsx'
import { Link } from 'react-router-dom'

// 2、声明路由
// to="/要跳转到的页面路由" 与 Route 中的 path 一致
<Link to="/about">About</Link>

// 3、注册路由
// component={要展示的组件}
<Route path="/home" component={Home}></Route>

方法二:通过字符串执行跳转路由

1
2
3
4
5
6
7
8
9
10

<Link to={{
pathname: '/login',
search: '?name=cedric',
hash: '#someHash',
state: { fromWechat: true }
}}>
<span>登录</span>
</Link>

  • pathname: 表示要链接到的路径的字符串。
  • search: 表示查询参数的字符串形式。
  • hash: 放入网址的 hash,例如 #a-hash。
  • state: 状态持续到 location。通常用于隐式传参(埋点),可以用来统计页面来源
  • 点击链接 进入 Login 页面后,就可以在 this.props.location.state 中看到 fromWechat: true

当 NavLink 与当前 URL 匹配时,使用 activeClassName 为其渲染元素添加样式属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

// 1、引入组件
import { NavLink } from 'react-router-dom'
import Home from './pages/Home/index.jsx'

// 2、声明路由

<NavLink
activeClassName="active"
className="list-group-item"
to="/home"
>
Home
</NavLink>

// 3、注册路由

<Route exact path="/" component={Home}></Route>

为了优化在编写多个路由链接时,<NavLink> 组件中 className 和 activeClassName 的代码冗余,需要将 < NavLink > 组件封装起来。

使用时导入 <MyNavLink> 并传入 to 和 title 就能显示多个路由链接

在 src/component 下新建文件夹,名为 MyNavLink,在里面新建名为 index 的 jsx 文件

如何向 <MyNavLink> 传递 title 呢?

1
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
30
31
32
33
34
35
// src/component/MyNavLink/index.jsx

import React, { Component } from 'react'
import { NavLink } from 'react-router-dom'

export default class MyNavLink extends Component {
render() {
{
/* 传标签属性
const { title } = this.props
return (
<NavLink
activeClassName="active"
className="list-group-item"
// 传递了to属性
{...this.props}
>
{title}
</NavLink>
) */
}

{
/*简写为:{...this.props} 将传递的标签属性全部接收,不用单独提取title,使用时类似于vue插槽,接受标签体内容*/
}

return (
<NavLink
activeClassName="active"
className="list-group-item"
{...this.props}
></NavLink>
)
}
}
1
2
3
4
5
6
7
8

// 声明路由时,向MyNavLink传递props,名为title,值为Home
// 方法一:传递标签属性:
<MyNavLink to='/home' title="Home" />

// 方法二:传递标签体内容
<MyNavLink to='/home'>Home</MyNavLink>

Switch 和 Router

Switch:渲染与该地址匹配的第一个子节点 <Route> 或者 <Redirect>,只是匹配到第一个路由后,就不再继续匹配。

Router:渲染时会从上至下检索路由地址是否匹配,若是匹配,则渲染全部组件

  • 1、通常情况下,patch 和 component 是一一对应的关系
  • 2、用 Switch 能提高路由匹配效率(单一匹配)
1
2
3
4
5
6
7
8
9
10

import { Switch } from 'react-router-dom'

// 此时:只显示Home组件

<Switch>
<Route path="/home" component={Home}></Route>
<Route path="/home" component={Test}></Route>
</Switch>

Redirect 重定向

导航到一个新的地址。即重定向。

1
2
3
4
5
6
7
8
9

import { Route, Redirect } from 'react-router-dom'

// 注册路由

<Route path="/home" component={Home}></Route>
<Redirect to="/home" from='/' />

// 当访问路由'/'时,会直接重定向到'/home'

路由器的模糊匹配和严格匹配

  1. 模糊匹配(默认)
  • 默认为模糊匹配模式
  • 输入的路径要包含匹配路径,并且顺序一致
  • 可以在默认的模糊匹配中向路由组件传递参数
  1. 严格(精确)匹配 —exact
  • 使用 exact = {true} 或 exact 开启严格匹配模式
  • 精确匹配路由,不要随便开启,有些时候开启会导致无法继续匹配二级路由

向路由组件传参的三种方式

在 src/pages/Home 下新建两个文件夹,名为 Message 和 News,在里面新建名为 index 的 jsx 文件。是 Home 页面的两个子页面

1
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
30
31
32
33
34
35
// src/pages/Home/Message/index.jsx
import React, { Component } from 'react'
import Detail from '../Home_Message_Detail/index'
import { Route, Link } from 'react-router-dom'

class Message extends Component {
state = {
messageArr: [
{ id: '01', title: '消息1' },
{ id: '02', title: '消息2' },
{ id: '03', title: '消息3' },
],
}

render() {
const { messageArr } = this.state
return (
<div>
<ul>
{messageArr.map((messageObj) => {
return (
<li key={messageObj.id}>
{/* 向路由组件传递params参数 代码区域*/}
</li>
)
})}
</ul>
<hr />
{/* 声明接收params参数 代码区域*/}
</div>
)
}
}

export default Message
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// src/pages/Home/News/index.jsx

import React, { Component } from 'react'

class News extends Component {
render() {
return (
<ul>
<li>news001</li>
<li>news002</li>
<li>news003</li>
</ul>
)
}
}

export default News

在 src/pages/Home/Message 下新建文件夹,名为 Detail,在里面新建名为 index 的 jsx 文件。是 Message 的子页面,用来接收 Message 页面传递过来的的参数。

1
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
// src/pages/Home/Message/Detail/index.jsx

import React, { Component } from 'react'

const DetailData = [
{ id: '01', content: '你好111' },
{ id: '02', content: '你好2222' },
{ id: '03', content: '你好33333' },
]

class Detail extends Component {
render() {
return (
{/* ??? 为代码待补充区域*/}
<div>
<ul>
<li>Id:???</li>
<li>Title:???</li>
<li>Content:???</li>
</ul>
</div>
)
}
}

export default Detail

在 src/pages/Home/index.jsx 组件中重新配置路由,根据路由显示 Message 和 News 两个子页面。

1
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
30
31
32
33
34
35
36
37
38
39
40
// src/pages/Home/index.jsx

import React, { Component } from 'react'
import News from '../Home_News/index'
import Message from '../Home_Message/index'
import MyNavLink from '../../component/MyNavLink/index'
import { Route, Switch, Redirect } from 'react-router-dom'

export default class Home extends Component {
render() {
return (
<div>
<h2>Home组件内容</h2>
<div>
<ul className="nav nav-tabs">
<li>
{/* 每次点击链接都从最开始的路由开始匹配
点击news后,路径被更改为/news,
然后又开始从App.jsx中匹配路由,
于是又匹配到重定向,跳转到about页面 */}
{/* <MyNavLink to='/news'>News</MyNavLink> */}
{/* 需要改为to="/home/news" */}
{<MyNavLink to="/home/news">News</MyNavLink>}
</li>
<li>
<MyNavLink to="/home/message">Message</MyNavLink>
</li>
</ul>

{/* 注册路由 */}
<Switch>
<Route path="/home/news" component={News}></Route>
<Route path="/home/message" component={Message}></Route>
<Redirect to="/home/news"></Redirect>
</Switch>
</div>
</div>
)
}
}

向路由组件传递 params 参数

Message 组件向 Detail 子组件传递 params 参数

在 “向路由组件传递 params 参数 代码区域” 中补充如下代码
通过 ${messageObj.id} 的方式传参

1
2
3
// src/pages/Home/Message/index.jsx

<Link to={`/home/message/detail/${messageObj.id}/${messageObj.title}`}>{messageObj.title}</Link>

在 “声明接收 params 参数 代码区域” 中补充如下代码
通过 :id 的方式声明接收

1
2
3
// src/pages/Home/Message/index.jsx

<Route path="/home/message/detail/:id/:title" component={Detail} />

Detail 子组件接收父组件 Message 的 params 参数

路由组件传递进来的参数在:this.props.match.params 中,是一个对象 Object 类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/pages/Home/Message/Detail/index.jsx

class Detail extends Component {
render() {
console.log(this.props.match.params)
/* 路由组件传递进来的参数在:
this.props.match.params中,是一个对象Object类型 */
const { id, title } = this.props.match.params
const findResult = DetailData.find((detailObj) => {
return detailObj.id === id
})
return (
<div>
<ul>
<li>Id:{id}</li>
<li>Title:{title}</li>
{/* 根据接收的参数id,匹配并显示data数组中的content */}
<li>Content:{findResult.content}</li>
</ul>
</div>
)
}
}

向路由组件传递 search 参数

Message 组件向 Detail 子组件传递 search 参数

在 “向路由组件传递 search 参数 代码区域” 中补充如下代码
通过?id=${messageObj.id}&title=${messageObj.title} 的方式传参

1
2
3
// src/pages/Home/Message/index.jsx

<Link to={`/home/message/detail/?id=${messageObj.id}&title=${messageObj.title}`}>{messageObj.title}</Link>

在 “声明接收 search 参数 代码区域” 中补充如下代码
search 参数无需接收,正常注册即可

1
2
3
4
// src/pages/Home/Message/index.jsx

{/* search参数无需接收,正常注册即可 */}
<Route path="/home/message/detail" component={Detail} />

Detail 子组件接收父组件 Message 的 search 参数

路由组件传递进来的 search 参数在:this.props.location.search 中。

由于接收到的参数为 key=value$key=value 的形式(urlencoded 编码),所以需要使用 querystring 库来拆分获取需要的数据。

queryString 的用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1、导入:querystring是react自带的库,无需下载
import qs from 'querystring'

// 2、querystring用法

// object => urlencoded(即xxx=xxx&xxx=xxx的格式)
let obj = {name: 'tom',age: 18}
console.log(qs.stringify(obj));
// 输出一段字符串:name=tom&age=18

// urlencoded => object
let str = 'carname=大众&price=19977'
console.log(qs.parse(str));
// 输出一个对象:{carname: '大众',price: '19977'}

在 Detail 组件中使用
1
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
30
31
32
33
// src/pages/Home/Message/Detail/index.jsx

import React, { Component } from 'react'
import qs from 'querystring'

const DetailData = [
{ id: '01', content: '你好111' },
{ id: '02', content: '你好2222' },
{ id: '03', content: '你好33333' },
]
class Detail extends Component {
render() {
console.log(this.props)
const { search } = this.props.location
const { id, title } = qs.parse(search.slice(1))
// slice截取掉问号(即:去除下标第0位)后转换成object的形式
const findResult = DetailData.find((detailObj) => {
return detailObj.id === id
})
return (
<div>
<ul>
<li>Id:{id}</li>
<li>Title:{title}</li>
{/* 根据接收的参数id,匹配并显示data数组中的content */}
<li>Content:{findResult.content}</li>
</ul>
</div>
)
}
}

export default Detail

向路由组件传递 state 参数

Message 组件向 Detail 子组件传递 state 参数

在 “向路由组件传递 state 参数 代码区域” 中补充如下代码
通过 state: {id: messageObj.id, title: messageObj.title} 的方式传参

1
2
3
// src/pages/Home/Message/index.jsx

<Link to={{ pathname: '/home/message/detail', state: { id: messageObj.id, title: messageObj.title } }}>{messageObj.title}</Link>

在 “声明接收 state 参数 代码区域” 中补充如下代码
state 参数无需接收,正常注册即可

1
2
3
4
// src/pages/Home/Message/index.jsx

{/* state参数无需接收,正常注册即可 */}
<Route path="/home/message/detail" component={Detail} />

Detail 子组件接收父组件 Message 的 state 参数

state 参数在地址栏没有显示数据,刷新也能保留住参数,
路由组件传递进来的 state 参数在:this.props.location.state

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// src/pages/Home/Message/Detail/index.jsx

class Detail extends Component {
render() {
// {}:防止对象为空时的报错
const { id, title } = this.props.location.state || {}
const findResult =
DetailData.find((detailObj) => {
return detailObj.id === id
}) || {}
return (
<div>
<ul>
<li>Id:{id}</li>
<li>Title:{title}</li>
{/* 根据接收的参数id,匹配并显示data数组中的content */}
<li>Content:{findResult.content}</li>
</ul>
</div>
)
}
}

路由的 push 和 replace

  • 路由默认使用 push 模式,在 history 堆栈添加一个新条目。
  • 在 路由中使用 replace 或者 replace={true},替换在 history 堆栈中的当前条目。
1
<MyNavLink replace to='/about'>About</MyNavLink>

编程式路由导航

借助 this.props.history 对象上的 Api 操作路由的跳转、前进、后退

  • this.props.history.push()
  • this.props.history.replace()
  • this.props.history.goBack()
  • this.props.history.goForward()
  • this.props.history.go (前进或后退的步数)

withRouter

  1. withRouter 可以将一个非路由组件包裹为路由组件,使这个非路由组件也能访问到当前路由的 match, location, history 对象,解决了在一般组件中使用路由组件 API 的问题。
  2. withRouter 的返回值是一个新组件