七、react-router 5
1.准备
1.1 SPA
而为了减少这样的情况,我们还有另一种应用,叫做 SPA ,单页应用程序
它比传统的 Web 应用程序更快,因为它们在 Web 浏览器本身而不是在服务器上执行逻辑。在初始页面加载后,只有数据来回发送,而不是整个 HTML,这会降低带宽。它们可以独立请求标记和数据,并直接在浏览器中呈现页面
1.2 什么是路由?
路由是根据不同的 URL 地址展示不同的内容或页面
在 SPA 应用中,大部分页面结果不改变,只改变部分内容的使用
一个路由其实就是一个映射关系(k:v)
key为路径,value可能是function 或者是 component
后端路由:
value是function,用来处理客户端提交的请求
注册路由:router.get(path,function(req,res))
工作过程:当node接收一个请求的时候,根据请求路径找到匹配的路由,调用路由中的函数来处理请求,返回响应的数据
前端路由:
浏览器端路由,value是Component,用于展示页面内容
注册路由:< Route path="/test" component={Test}>
工作过程:当浏览器的path变为/test的时候,当前路由组件就会变成Test组件
前端路由的优缺点
优点
用户体验好,不需要每次都从服务器全部获取整个 HTML,快速展现给用户
缺点
- SPA 无法记住之前页面滚动的位置,再次回到页面时无法记住滚动的位置
- 使用浏览器的前进和后退键会重新请求,没有合理利用缓存
1.3 前端路由的原理
前端路由的主要依靠的时 history ,也就是浏览器的历史记录
history 是 BOM 对象下的一个属性,在 H5 中新增了一些操作 history 的 API
浏览器的历史记录就类似于一个栈的数据结构,前进就相当于入栈,后退就相当于出栈
并且历史记录上可以采用 listen
来监听请求路由的改变,从而判断是否改变路径
在 H5 中新增了 createBrowserHistory
的 API ,用于创建一个 history 栈,允许我们手动操作浏览器的历史记录
新增 API:pushState
,replaceState
,原理类似于 Hash 实现。 用 H5 实现,单页路由的 URL 不会多出一个 #
号,这样会更加的美观
2. Link、BrowserRouter、Route
react的路由有三类:
web【主要适用于浏览器】,native【主要适用于安卓和IOS】,anywhere【任何地方】
在这主要使用web也就是这个标题 react-router-dom
专门给 web 人员使用的库
- 一个 react 的仓库
- 很常用,基本是每个应用都会使用的这个库
- 专门来实现 SPA 应用
安装:
yarn add react-router-dom@5
## npm install react-router-dom@5
首先我们要明确好页面的布局 ,分好导航区、展示区
要引入 react-router-dom
库,暴露一些属性 Link、BrowserRouter...
import { Link, BrowserRouter, Route } from 'react-router-dom'
将整个 App 组件标签采用 BrowserRouter
标签(或者HashRouter
标签) 去包裹,这样整个 App 组件都在一个路由器的管理下:
BrowserRouter
类似于这样:http://127.0.0.1:3000/about
(不带#号)
HashRouter
类似于这样:http://127.0.0.1:3000/#/about
(带#号)
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
)
导航区的 a 标签改为 Link 标签
{/* 跳转到/about路径 */}
<Link className="list-group-item" to="/about">About</Link>
{/* 跳转到/home路径 */}
<Link className="list-group-item" to="/home">Home</Link>
同时我们需要用 Route
标签,来进行路径的匹配,从而实现不同路径的组件切换
{/* 如果路径是/about,展示区就展示About组件 */}
<Route path="/about" component={About}></Route>
{/* 如果路径是/about,展示区就展示Home组件 */}
<Route path="/home" component={Home}></Route>
这样之后我们还需要一步,加个路由器,在上面我们写了两组路由,同时还会报错指示我们需要添加 Router
来解决错误,这就是需要我们添加路由器来管理路由,如果我们在 Link 和 Route 中分别用路由器管理,那这样是实现不了的,只有在一个路由器的管理下才能进行页面的跳转工作。
效果:
需要在
public/index.html
文件里引入bootstrap.css
样式
默认:

点击About
组件

点击Home
组件

完整代码
src/App.jsx
import React, { Component } from 'react'
import {Link,Route} from 'react-router-dom'
import Home from './components/Home'
import About from './components/About'
export default class App extends Component {
render() {
return (
<div>
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header"><h2>React Router Demo</h2></div>
</div>
</div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 原生html中,靠<a>跳转不同的页面 */}
{/* <a className="list-group-item" href="./about.html">About</a>
<a className="list-group-item active" href="./home.html">Home</a> */}
{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
{/* 跳转到/about路径 */}
<Link className="list-group-item" to="/about">About</Link>
{/* 跳转到/home路径 */}
<Link className="list-group-item" to="/home">Home</Link>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 注册路由 */}
{/* 如果路径是/about,展示区就展示About组件 */}
<Route path="/about" component={About}/>
{/* 如果路径是/about,展示区就展示Home组件 */}
<Route path="/home" component={Home}/>
</div>
</div>
</div>
</div>
</div>
)
}
}
src/components/About/index.jsx
import React, { Component } from 'react'
export default class About extends Component {
render() {
return (
<h3>我是About的内容</h3>
)
}
}
src/components/Home/index.jsx
import React, { Component } from 'react'
export default class Home extends Component {
render() {
return (
<h3>我是Home的内容</h3>
)
}
}
src/index.js
//引入react核心库
import React from 'react'
//引入ReactDOM
import ReactDOM from 'react-dom/client'
//
import { BrowserRouter, HashRouter } from 'react-router-dom'
//引入App
import App from './App'
// const root = ReactDOM.createRoot(document.getElementById('root'))
// root.render(<HashRouter><App /></HashRouter>)
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
)
3.路由组件和一般组件
在我们前面的内容中,我们是把组件 Home 和组件 About 当成是一般组件来使用,我们将它们写在了 src 目录下的 components 文件夹下,但是我们又会发现它和普通的组件又有点不同,对于普通组件而言,我们在引入它们的时候我们是通过标签的形式来引用的。但是在上面我们可以看到,我们把它当作路由来引用时,我们是通过 {Home}
来引用的。
从这一点我们就可以认定一般组件和路由组件存在着差异
3.1 写法不同
一般组件:
<Demo/>
路由组件:
<Route path="/demo" component={Demo}/>
3.2 存放的位置不同
为了规范我们的书写,一般将路由组件放在 pages
/views
文件夹中,路由组件放在 components
而最重要的一点就是它们接收到的 props
不同,在一般组件中,如果我们不进行传递,就不会收到值。而对于路由组件而言,它会接收到 3 个固定属性 history
、location
以及 match
。
我们在Home
组件的render
函数里输出this.props
就会看到如下内容:

重要的属性
history:
go: ƒ go(n)
goBack: ƒ goBack()
goForward: ƒ goForward()
push: ƒ push(path, state)
replace: ƒ replace(path, state)
location:
pathname: "/about"
search: ""
state: undefined
match:
params: {}
path: "/about"
url: "/about"
4.NavLink 标签
4.1 基本使用
NavLink 标签和 Link 标签的作用基本相同,但NavLink可以给激活的路由菜单设置className,从而给激活的路由菜单设置样式。
在前面的 demo 中,你可能会发现点击的按钮并没有出现高亮的效果,正常情况下我们给标签多添加一个 active
的类就可以实现高亮的效果,而 NavLink 标签正可以帮助我们实现这一步,当我们选中某个 NavLink 标签时,就会自动的在类上添加一个 active
属性。
<NavLink className="list-group-item" to="/about">About</NavLink>

当然 NavLink 标签是默认的添加上 active
类,我们也可以改变它,在标签上添加一个属性 activeClassName
如下代码,就写了activeClassName
,当点击的时候就会触发我们自定义的class
<NavLink activeClassName="highlight" className="list-group-item" to="/about">About</NavLink>
<NavLink activeClassName="highlight" className="list-group-item" to="/home">Home</NavLink>
注意:我们要在
src/App.css
里添加如下样式,并在src/App.jsx
里使用import './App.css'
引入样式.highlight { background-color: orange; }

4.2 NavLink 封装
在上面的 NavLink 标签种,我们可以发现我们每次都需要重复的去写这些样式名称或者是 activeClassName
,这并不是一个很好的情况,代码过于冗余。那我们是不是可以想想办法封装一下它们呢?
我们可以创建一个名为 MyNavLink
的自定义组件,并对 NavLink 进行封装。
首先我们需要新建一个 MyNavLink 组件,然后render里返回如下结构
// 通过{...对象}的形式解析对象,相当于将对象中的属性全部展开
<NavLink className="list-group-item" {...this.props} />
我们在标签体内写的内容都会成为一个 children
属性,因此我们在调用 MyNavLink
时,在标签体中写的内容,都会成为 props
中的一部分,我们的自定义组件从而能够实现NavLink
组件的全部功能
接下来我们在调用时,直接写如下代码即可,这样可以省掉className="list-group-item"
{/*将NavLink进行封装,成为MyNavLink,通过props进行传参数,标签体内容props是特殊的一个属性,叫做children */}
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink>
详细步骤:
推荐通用组件放在
src/components
里,路由组件放在src/pages
里面
src/components/MyNavLink/index.jsx
import React, { Component } from 'react'
import { NavLink } from 'react-router-dom'
export default class MyNavLink extends Component {
render() {
console.log(this.props);
return (
<NavLink className="list-group-item" {...this.props} />
)
}
}
将src/App.jsx
里的NavLink
组件替换为我们自定义的MyNavLink
组件
{/* <NavLink className="list-group-item" to="/test/about">About</NavLink> */}
<MyNavLink to="/about">About</MyNavLink>
{/* <NavLink className="list-group-item" to="/test/home">Home</NavLink> */}
<MyNavLink to="/home">Home</MyNavLink>
可以看到,使用MyNavLink
组件时标签体的内容被放到this.props
的children
里面了这样我们使用{...this.props}
也会把children
带给NavLink
组件,使得NavLink
组件也能获取到标签体的内容

5.解决多级路由样式丢失的问题
如果我们把上面的一级路由改为二级路由(改成其他多级路由也一样)
src/App.jsx
<MyNavLink to="/test/about">About</MyNavLink>
<MyNavLink to="/test/home">Home</MyNavLink>
<Route path="/test/about" component={About} />
<Route path="/test/home" component={Home} />
我们在 http://127.0.0.1:3000/ 页面里点击 About
,来到了 http://127.0.0.1:3000/test/about 页面,可以看到能够正确显示

此时我们刷新页面,就会发现样式丢失了,请求路径为 http://127.0.0.1:3000/test/css/bootstrap.css 而正确的请求路径为 http://127.0.0.1:3000/css/bootstrap.css 错误的路径比正确的多了一个/test
。

这是因为我们在public/index.html
文件里引入bootstrap.css
是通过<link rel="stylesheet" href="./css/bootstrap.css">
这种相对路径的方式引入的,而我们整个项目就一个index.html
,如果我们的请求路径为 http://127.0.0.1:3000/test/about 那么./css/bootstrap.css
请求的就是 http://127.0.0.1:3000/css/bootstrap.css 由于请求的这个路径找不到,所以就给我们返回了index.html 的内容了

解决这个问题,有三个方法:
1.样式加载使用绝对位置
<link href="/css/bootstrap.css" rel="stylesheet">
2.使用 %PUBLIC_URL%
%PUBLIC_URL%
表示项目的根路径
<link href="%PUBLIC_URL%/css/bootstrap.css" rel="stylesheet">
3.使用HashRouter
因为HashRouter会添加#,默认不会处理#后面的路径(#后面的路径是锚点,不属于请求路径的一部分),所以也是可以解决的
6.模糊匹配和精准匹配
路由的匹配有两种形式,一种是模糊匹配一种是精准匹配,React 中默认开启的是模糊匹配
模糊匹配:在匹配路由时,只要访问的路径的前半部分与我们配置的路径匹配就可以了
精准匹配:请求的路径必须和我们配置的路径相同
模糊匹配
比如访问/home/a/b
路径
<MyNavLink to = "/home/a/b" >Home</MyNavLink>
此时该标签匹配的路由,分为三个部分 home a b;将会根据这个先后顺序匹配路由。
如下就可以匹配到相应的路由:
<Route path="/home"component={Home}/>
但是如果配置为如下路径就会失败,它是根据路径一级一级查询的,可以包含前面那一部分,并不是只包含部分就可以。
<Route path="/a" component={Home}/>
精准匹配
我们可以给Route组件添加exact={true}
属性,这样就开启了精准匹配
如下两种方式都能开启精准匹配,开启后就只能精确的匹配/home,则上面的/home/a/b就不行了
<Route exact={true} path="/home" component={Home}/>
<Route exact path="/home" component={Home}/>
7.Switch 解决相同路径问题
我们将注册路由的部分改为如下代码
<Route path="/home" component={Home}></Route>
<Route path="/about" component={About}></Route>
<Route path="/about" component={About}></Route>
这是两个路由组件,在2,3行中,我们同时使用了相同的路径 /about

我们发现它出现了两个 about
组件的内容,那这是为什么呢?
其实是因为Route
的机制,当匹配上了第一个 /about
组件后,它还会继续向下匹配,因此会出现两个 About 组件,这时我们可以采用 Switch
组件进行包裹,这样匹配到一个路由后就不会再往下匹配了。
我们需要先从
react-router-dom
中引入Switch
组件,才能使用import { Route, Switch } from 'react-router-dom'
<Switch>
<Route path="/home" component={Home}></Route>
<Route path="/about" component={About}></Route>
<Route path="/about" component={About}></Route>
</Switch>

8.路由重定向(默认路由)
在配置好路由,最开始打开页面的时候,应该是不会匹配到任意一个组件。这个时候页面就显得极其不合适,此时应该默认的匹配到一个组件。
这个时候我们就需要时候 Redirecrt 进行默认匹配了。
<Redirect to="/home" />
当我们加上这条语句时,页面找不到指定路径时,就会重定向到 /home
页面下因此当我们请求3000端口时,就会重定向到 /home
这样就能够实现我们想要的效果了
如下的代码就是默认匹配/home路径所到的组件
<Switch>
<Route path="/about" component={About} />
{/* exact={true}:开启严格匹配的模式,路径必须一致 */}
<Route path="/home" component={Home} />
{/* Redirect:如果上面的都没有匹配到,就匹配到这个路径下面 */}
<Redirect to="/home" />
</Switch>
9.嵌套路由
嵌套路由也就是我们前面有提及的二级路由,但是嵌套路由包括了二级、三级...还有很多级路由,
需求:我们需要在内容区域中添加头部
和详细内容
两个部分
我们将我们的嵌套内容写在相应的组件里面,这个是在 Home 组件的 return 内容
<div>
<h2>Home组件内容</h2>
<div>
<ul className="nav nav-tabs">
<li>
<MyNavLink className="list-group-item" to="/home/news">News</MyNavLink>
</li>
<li>
<MyNavLink className="list-group-item " to="/home/message">Message</MyNavLink>
</li>
</ul>
{/* 注册路由 */}
<Switch>
<Route path="/home/news" component={News} />
<Route path="/home/message" component={Message} />
</Switch>
</div>
</div>
在这里我们需要使用嵌套路由的方式,才能完成匹配
首先我们得 React 中路由得注册是有顺序的,我们在匹配得时候,因为 Home 组件是先注册得,因此在匹配的时候先去找 home 路由,由于是模糊匹配,会成功的匹配
在 Home 组件里面去匹配相应的路由,从而找到 /home/news 进行匹配,因此找到 News 组件,进行匹配渲染
如果开启精确匹配的话,第一步的
/home/news
匹配不到/home
,这个时候就不会显示我们想要的页面了!
完整代码
react_staging
├─ public // 公共资源
├─ src // 源码文件夹
│ ├─ components
│ │ └─ MyNavLink
│ │ └─ index.jsx
│ ├─ pages
│ │ ├─ About
│ │ │ └─ index.jsx
│ │ └─ Home
│ │ ├─ News
│ │ │ └─ index.jsx
│ │ ├─ Message
│ │ │ └─ index.jsx
│ │ └─ index.lsx
│ ├─ App.jsx // App组件
│ └─ index.js // js入口文件
├─ .gitignore // 自动创建本地仓库
├─ package.json // 相关配置文件
├─ README.md // 项目说明文档
└─ yarn.lock
src/App.jsx
import React, { Component } from 'react'
import { Route, Switch,Redirect } from 'react-router-dom'
import Home from './pages/Home'
import About from './pages/About'
import MyNavLink from './components/MyNavLink'
import './App.css'
export default class App extends Component {
render() {
return (
<div>
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header"><h2>React Router Demo</h2></div>
</div>
</div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 注册路由 */}
<Switch>
<Route path="/about" component={About} />
{/* exact={true}:开启严格匹配的模式,路径必须一致 */}
<Route path="/home" component={Home} />
{/* Redirect:如果上面的都没有匹配到,就匹配到这个路径下面 */}
<Redirect to="/home" />
</Switch>
</div>
</div>
</div>
</div>
</div>
)
}
}
src/pages/Home/index.jsx
import React, { Component } from 'react'
import { Route, Switch } from 'react-router-dom'
import MyNavLink from '../../components/MyNavLink'
import News from './News'
import Message from './Message'
export default class Home extends Component {
render() {
return (
<div>
<h2>Home组件内容</h2>
<div>
<ul className="nav nav-tabs">
<li>
<MyNavLink className="list-group-item" to="/home/news">News</MyNavLink>
</li>
<li>
<MyNavLink className="list-group-item " to="/home/message">Message</MyNavLink>
</li>
</ul>
{/* 注册路由 */}
<Switch>
<Route path="/home/news" component={News} />
<Route path="/home/message" component={Message} />
</Switch>
</div>
</div>
)
}
}
src/pages/Home/News/index.jsx
import React, { Component } from 'react'
export default class News extends Component {
render() {
return (
<ul>
<li>news001</li>
<li>news002</li>
<li>news003</li>
</ul>
)
}
}
src/pages/Home/Message/index.jsx
import React, { Component } from 'react'
export default class Message extends Component {
render() {
return (
<div>
<ul>
<li>
<a href="/message1">message001</a>
</li>
<li>
<a href="/message2">message002</a>
</li>
<li>
<a href="/message/3">message003</a>
</li>
</ul>
</div>
)
}
}
src/pages/About/index.jsx
import React, { Component } from 'react'
export default class About extends Component {
render() {
return (
<h3>我是About的内容</h3>
)
}
}
src/components/MyNavLink/index.jsx
import React, { Component } from 'react'
import { NavLink } from 'react-router-dom'
export default class MyNavLink extends Component {
render() {
console.log(this.props);
return (
<NavLink className="list-group-item" {...this.props} />
)
}
}
src/index.js
//引入react核心库
import React from 'react'
//引入ReactDOM
import ReactDOM from 'react-dom/client'
//
import { BrowserRouter, HashRouter } from 'react-router-dom'
//引入App
import App from './App'
// const root = ReactDOM.createRoot(document.getElementById('root'))
// root.render(<HashRouter><App /></HashRouter>)
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
)
10.传递参数
需求:我们需要实现的效果是,点击消息列表,展示出消息的详细内容,此时就需要给子组件传递数据。
路由传参有3种方式,分别是 传递 params 参数
、传递 search 参数
、传递 state 参数

10.1 传递 params 参数
上面的案例使用的就是传递 params 参数
的方式,传递的数据就在请求的路径上
首先需要将详细内容的数据列表,保存在 DetailData 中,将消息列表保存在 Message 的 state 中。
然后可以通过将数据拼接在路由地址末尾上来实现数据的传递
<Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
如上,我们将消息列表的 id 和 title 写在了路由地址后面
这里我们需要注意的是:需要采用模板字符串以及
$
符的方式来进行数据的获取
在注册路由时,我们可以通过 :参数名
来传递数据
<Route path="/home/message/detail/:id/:title" component={Detail} />
如上,使用了 :id/:title
成功的接收了由 Link 传递过来的 id 和 title 数据
这样我们既成功的实现了路由的跳转,又将需要获取的数据传递给了 Detail 组件
我们在 Detail 组件中打印 this.props
来查看当前接收的数据情况

我们可以发现,我们传递的数据被接收到了对象的 match 属性下的 params 中
因此我们可以在 Detail 组件中获取到了 Message 组件中传递来的 params 数据
并通过 params 数据中的 id
值,在详细内容的数据集中查找出指定 id
的详细内容
const { id, title } = this.props.match.params
const findResult = DetailData.find((detailObj) => {
return detailObj.id === id
})
最后渲染数据即可。
完整代码:
src/pages/Home/Message/index.jsx
import React, { Component } from 'react'
import {Link,Route} from 'react-router-dom'
import Detail from './Detail'
export default 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((msgObj)=>{
return (
<li key={msgObj.id}>
{/* 向路由组件传递params参数 */}
<Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
</li>
)
})
}
</ul>
<hr/>
{/* 声明接收params参数 */}
<Route path="/home/message/detail/:id/:title" component={Detail}/>
</div>
)
}
}
src/pages/Home/Message/Detail/index.jsx
import React, { Component } from 'react'
const DetailData = [
{id:'01',content:'你好,中国'},
{id:'02',content:'你好,尚硅谷'},
{id:'03',content:'你好,未来的自己'}
]
export default class Detail extends Component {
render() {
console.log(this.props);
// 接收params参数
const {id,title} = this.props.match.params
const findResult = DetailData.find((detailObj)=>{
return detailObj.id === id
})
return (
<ul>
<li>ID:{id}</li>
<li>TITLE:{title}</li>
<li>CONTENT:{findResult.content}</li>
</ul>
)
}
}
10.2 传递 search 参数
我们还可以采用传递 search 参数的方法来实现
首先我们先确定数据传输的方式,我们先在 Link 中采用 ?
符号的方式来表示后面的为可用数据
<Link to={`/home/message/detail?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>
采用 search
传递的方式,无需在 Route 中再次声明,可以在 Detail 组件中直接获取到

我们可以发现,我们的数据保存在了 location
对象下的 search
中,是一种字符串的形式保存的,我们可以引用qs
库来进行转化
qs是一个npm仓库所管理的包,可通过
npm install qs
命令进行安装,默认情况下可以直接使用,而无需安装import qs from 'qs'
- qs.parse()将URL解析成对象的形式
const str = "username='admin'&password='123456'"; console.log(qs.parse(str)); // Object { username: "admin", password: "123456" }
- qs.stringify()将对象 序列化成URL的形式,以&进行拼接
const a = qs.stringify({ username: 'admin', password: '123456' }); console.log(a); // username=admin&password=123456
我们可以采用 parse
方法,将字符串转化为键值对形式的对象
const { search } = this.props.location
const { id, title } = qs.parse(search.slice(1)) // 从?后面开始截取字符串
这样我们就能成功的获取数据,并进行渲染
完整代码
src/pages/Home/Message/index.jsx
import React, { Component } from 'react'
import { Link, Route } from 'react-router-dom'
import Detail from './Detail'
export default 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((msgObj) => {
return (
<li key={msgObj.id}>
{/* 向路由组件传递search参数 */}
<Link to={`/home/message/detail?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>
</li>
)
})
}
</ul>
<hr />
{/* search参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail} />
</div>
)
}
}
src/pages/Home/Message/Detail/index.jsx
import React, { Component } from 'react'
import qs from 'qs'
const DetailData = [
{id:'01',content:'你好,中国'},
{id:'02',content:'你好,尚硅谷'},
{id:'03',content:'你好,未来的自己'}
]
export default class Detail extends Component {
render() {
// 接收search参数
const {search} = this.props.location
const {id,title} = qs.parse(search.slice(1))
const findResult = DetailData.find((detailObj)=>{
return detailObj.id === id
})
return (
<ul>
<li>ID:{id}</li>
<li>TITLE:{title}</li>
<li>CONTENT:{findResult.content}</li>
</ul>
)
}
}
10.3 传递 state 参数
传递 state 参数不会将数据携带到地址栏上,采用内部的状态来维护。如果使用BrowserRouter
即使刷新页面参数也不会丢失(使用HashRouter
会丢失)
<Link to={{ pathname: '/home/message/detail', state: { id: msgObj.id, title: msgObj.title } }}>{msgObj.title}</Link>
首先,我们需要在 Link 中注册跳转时,传递一个路由对象,包括一个 跳转地址名,一个 state 数据,这样我们就可以在 Detail 组件中获取到这个传递的 state 数据
注意:采用这种方式传递,无需声明接收
我们可以在 Detail 组件中的 location 对象下的 state 中取出我们所传递的数据
const { id, title } = this.props.location.state
解决清除缓存造成报错的问题,我们可以在获取不到数据的时候用空对象来替代,例如,
const { id, title } = this.props.location.state || {}
当获取不到 state
时,则用空对象代替
这里的 state 和状态里的 state 有所不同
完整代码
src/pages/Home/Message/index.jsx
import React, { Component } from 'react'
import { Link, Route } from 'react-router-dom'
import Detail from './Detail'
export default 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((msgObj) => {
return (
<li key={msgObj.id}>
{/* 向路由组件传递state参数 */}
<Link to={{ pathname: '/home/message/detail', state: { id: msgObj.id, title: msgObj.title } }}>{msgObj.title}</Link>
</li>
)
})
}
</ul>
<hr />
{/* state参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail} />
</div>
)
}
}
src/pages/Home/Message/Detail/index.jsx
import React, { Component } from 'react'
import qs from 'qs'
const DetailData = [
{id:'01',content:'你好,中国'},
{id:'02',content:'你好,尚硅谷'},
{id:'03',content:'你好,未来的自己'}
]
export default class Detail extends Component {
render() {
// 接收search参数
const {search} = this.props.location
const {id,title} = qs.parse(search.slice(1))
const findResult = DetailData.find((detailObj)=>{
return detailObj.id === id
})
return (
<ul>
<li>ID:{id}</li>
<li>TITLE:{title}</li>
<li>CONTENT:{findResult.content}</li>
</ul>
)
}
}
10.4 小结
1、params参数
路由链接(携带参数):<Link to='/demo/test/tom/18'}>详情</Link>
注册路由(声明接收):<Route path="/demo/test/:name/:age" component={Test}/>
接收参数:this.props.match.params
2、search参数
路由链接(携带参数):<Link to='/demo/test?name=tom&age=18'}>详情</Link>
注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
接收参数:this.props.location.search
备注:获取到的search是urlencoded编码字符串,需要借助qs解析
3、state参数
路由链接(携带参数):<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>
注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
接收参数:this.props.location.state
备注:刷新也可以保留住参数
接收参数
// 接收params参数
const {id,title} = this.props.match.params
// 接收search参数
import qs from 'qs'
const {search} = this.props.location
const {id,title} = qs.parse(search.slice(1))
// 接收state参数
const {id,title} = this.props.location.state || {}
完整代码
src/pages/Home/Message/index.jsx
import React, { Component } from 'react'
import {Link,Route} from 'react-router-dom'
import Detail from './Detail'
export default 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((msgObj)=>{
return (
<li key={msgObj.id}>
{/* 向路由组件传递params参数 */}
{/* <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link> */}
{/* 向路由组件传递search参数 */}
{/* <Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link> */}
{/* 向路由组件传递state参数 */}
<Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>
</li>
)
})
}
</ul>
<hr/>
{/* 声明接收params参数 */}
{/* <Route path="/home/message/detail/:id/:title" component={Detail}/> */}
{/* search参数无需声明接收,正常注册路由即可 */}
{/* <Route path="/home/message/detail" component={Detail}/> */}
{/* state参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail}/>
</div>
)
}
}
src/pages/Home/Message/Detail/index.jsx
import React, { Component } from 'react'
// import qs from 'qs'
const DetailData = [
{id:'01',content:'你好,中国'},
{id:'02',content:'你好,尚硅谷'},
{id:'03',content:'你好,未来的自己'}
]
export default class Detail extends Component {
render() {
console.log(this.props);
// 接收params参数
// const {id,title} = this.props.match.params
// 接收search参数
// const {search} = this.props.location
// const {id,title} = qs.parse(search.slice(1))
// 接收state参数
const {id,title} = this.props.location.state || {}
const findResult = DetailData.find((detailObj)=>{
return detailObj.id === id
}) || {}
return (
<ul>
<li>ID:{id}</li>
<li>TITLE:{title}</li>
<li>CONTENT:{findResult.content}</li>
</ul>
)
}
}
11.路由跳转
11.1 push 与 replace 模式
默认情况下,开启的是 push 模式,也就是说,每次点击跳转,都会向栈中压入一个新的地址,在点击返回时,可以返回到上一个打开的地址,
当我们在读消息的时候,有时候我们可能会不喜欢这种繁琐的跳转,我们可以开启 replace 模式,这种模式与 push 模式不同,它会将当前地址替换成点击的地址,也就是替换了新的栈顶
我们只需要在需要开启的链接上加上 replace
即可
<Link replace to={{ pathname: '/home/message/detail', state: { id: msgObj.id, title: msgObj.title } }}>{msgObj.title}</Link>
11.2 编程式路由导航
借助this.prosp.history对象上的API对操作路由跳转、前进、后退
this.prosp.history.push()
this.prosp.history.replace()
this.prosp.history.goBack()
this.prosp.history.goForward()
this.prosp.history.go(1)
我们可以采用绑定事件的方式实现路由的跳转,我们在按钮上绑定一个 onClick
事件,当事件触发时,我们执行一个回调
//push跳转+携带params参数
// this.props.history.push(`/home/message/detail/${id}/${title}`)
//push跳转+携带search参数
// this.props.history.push(`/home/message/detail?id=${id}&title=${title}`)
//push跳转+携带state参数
this.props.history.push(`/home/message/detail`,{id,title})
//replace跳转+携带params参数
//this.props.history.replace(`/home/message/detail/${id}/${title}`)
//replace跳转+携带search参数
// this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`)
//replace跳转+携带state参数
this.props.history.replace(`/home/message/detail`,{id,title})
11.3 withRouter
当我们需要在页面内部添加回退前进等按钮时,由于这些组件我们一般通过一般组件的方式去编写,因此我们会遇到一个问题,无法获得 history 对象,这正是因为我们采用的是一般组件造成的。
只有路由组件才能获取到 history 对象
因此我们需要如何解决这个问题呢
我们可以利用 react-router-dom
对象下的 withRouter
函数来对我们导出的 Header
组件进行包装,这样我们就能获得一个拥有 history
对象的一般组件
我们需要对哪个组件包装就在哪个组件下引入
// Header/index.jsx
import { withRouter } from 'react-router-dom'
// 在最后导出对象时,用 `withRouter` 函数对 index 进行包装
export default withRouter(index);
这样就能让一般组件获得路由组件所特有的 API

完整代码
src/components/Header/index.jsx
import React, { Component } from 'react'
import { withRouter } from 'react-router-dom'
class Header extends Component {
render() {
console.log('Header组件收到的props是',this.props);
return (
<div className="page-header"><h2>React Demo</h2></div>
)
}
}
export default withRouter(Header)
src/App.jsx
import React, { Component } from 'react'
import Header from './components/Header'
import './App.css'
export default class App extends Component {
render() {
return (
<div>
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<Header />
</div>
</div>
</div>
)
}
}
12.BrowserRouter 和 HashRouter 的区别
它们的底层实现原理不一样
对于 BrowserRouter 来说它使用的是 React 为它封装的 history API ,这里的 history 和浏览器中的 history 有所不同噢!通过操作这些 API 来实现路由的保存等操作,但是这些 API 是 H5 中提出的,因此不兼容 IE9 以下版本。
对于 HashRouter 而言,它实现的原理是通过 URL 的哈希值,我们可以理解为是锚点跳转,因为锚点跳转会保存历史记录,从而让 HashRouter 有了相关的前进后退操作,HashRouter 不会将 #
符号后面的内容当作请求地址的一部分。兼容性更好!
地址栏的表现形式不一样
- HashRouter 的路径中包含
#
,例如localhost:3000/#/demo/test
刷新后路由 state 参数改变
- 在BrowserRouter 中,state 保存在history 对象中,刷新不会丢失
- HashRouter 则刷新会丢失 state
八、react-router 6
关于路由的知识已在
【react-router 5】
中进行说明,这里主要是对于5版本的api的变换说明
1.概述
官方文档:Home v6.4.1 | React Router 以三个不同的包发布到 npm 上,它们分别为:
- react-router: 路由的核心库,提供了很多的:组件、钩子。
- react-router-dom: 包含react-router所有内容,并添加一些专门用于 DOM 的组件,例如
<BrowserRouter>
等 。 - react-router-native: 包括react-router所有内容,并添加一些专门用于ReactNative的API,例如:
<NativeRouter>
等。
与React Router 5.x 版本相比,改变了什么?
内置组件的变化:移除
<Switch/>
,新增<Routes/>
等。语法的变化:
component={About}
变为element={<About/>}
等。新增多个hook:
useParams
、useNavigate
、useMatch
等。官方明确推荐函数式组件了!!!
......
安装
yarn add react-router-dom@6
## npm install react-router-dom@6
2.BrowserRouter和HashRouter
在 React Router 中,最外层的 API 通常就是用 BrowserRouter。BrowserRouter 的内部实现是用了 history
这个库和 React Context 来实现的,所以当你的用户前进后退时,history
这个库会记住用户的历史记录,这样需要跳转时可以直接操作。
BrowserRouter 使用时,通常用来包住其它需要路由的组件,所以通常会需要在你的应用的最外层用它,比如如下
import ReactDOM from 'react-dom'
import * as React from 'react'
import { BrowserRouter } from 'react-router-dom'
import App from './App`
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>
, document.getElementById('app))
BrowserRouter
和<HashRouter>
- 说明:
<HashRouter>
作用与<BrowserRouter>
一样,但<HashRouter>
修改的是地址栏的hash值。 - 区别:HashRouter 的路径中包含
#
,例如localhost:3000/#/demo/test
;而 BrowserRouter 的路径不含,直接为localhost:3000/demo/test
- 备注:6.x版本中
<HashRouter>
、<BrowserRouter>
的用法与 5.x 相同。
3.Routes 与 Route
v6版本中移出了先前的
<Switch>
,引入了新的替代者:<Routes>
。<Routes>
和<Route>
要配合使用,且必须要用<Routes>
包裹<Route>
。<Route>
相当于一个 if 语句,如果其路径与当前 URL 匹配,则呈现其对应的组件。<Route caseSensitive>
属性用于指定:匹配时是否区分大小写(默认为 false)。当URL发生变化时,
<Routes>
都会查看其所有子<Route>
元素以找到最佳匹配并呈现组件 。<Route>
也可以嵌套使用,且可配合useRoutes()
配置 “路由表” ,但需要通过<Outlet>
组件来渲染其子路由。
Route
Route 用来定义一个访问路径与 React 组件之间的关系。比如说,如果你希望用户访问 https://your_site.com/about
的时候加载 <About />
这个 React 页面,那么你就需要用 Route:
{/* router5的写法:<Route path="/about" component={About}/> */}
<Route path="/about" element={<About />} />
Routes(<Switch>
的替代者)
Routes 是用来包住路由访问路径(Route)的。它决定用户在浏览器中输入的路径到对应加载什么 React 组件,因此绝大多数情况下,Routes 的唯一作用是用来包住一系列的 Route
,比如如下
import { Routes, Route } from "react-router-dom";
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
);
}
在这里,Routes 告诉了 React Router 每当用户访问根地址时,加载 Home
这个页面,而当用户访问 /about
时,就加载 <About />
页面。
完整代码
<Routes>
{/* path属性用于定义路径,element属性用于定义当前路径所对应的组件 */}
<Route path="/login" element={<Login />}></Route>
{/* 用于定义嵌套路由,home是一级路由,对应的路径/home */}
<Route path="home" element={<Home />}>
{/* test1 和 test2 是二级路由,对应的路径是/home/test1 或 /home/test2 */}
<Route path="test1" element={<Test />}></Route>
<Route path="test2" element={<Test2 />}></Route>
</Route>
{/* Route也可以不写element属性, 这时就是用于展示嵌套的路由 .所对应的路径是/users/xxx */}
<Route path="users">
<Route path="xxx" element={<Demo />} />
</Route>
</Routes>
4.React Router 实操案例
首先我们建起几个页面
<Home />
<About />
<Dashboard />
Home
用于展示一个简单的导航列表,About
用于展示关于页,而 Dashboard
则需要用户登录以后才可以访问。
import './App.css';
import { BrowserRouter, Route, Routes } from "react-router-dom"
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
</Routes>
</BrowserRouter>
)
}
const Home = () => {
return <div>hello world</div>
}
export default App;
这里我们直接在 App.js
中加上一个叫 Home 的组件,里面只是单纯地展示 hello wolrd
而已。接下来,我们再把另外两个路径写好,加入 About 和 Dashboard 两个组件
import './App.css';
import { BrowserRouter, Route, Routes } from "react-router-dom"
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</BrowserRouter>
)
}
const Home = () => {
return <div>hello world</div>
}
const About = () => {
return <div>这里是卡拉云的主页</div>
}
const Dashboard = () => {
return <div>今日活跃用户: 42</div>
}
export default App;
此时,当我们在浏览器中切换到 /
或 /about
或 /dashboard
时,就会显示对应的组件了。注意,在上面每个 Route
中,用 element
项将组件传下去,同时在 path
项中指定路径。在 Route
外,用 Routes
包裹起整路由列表。
5.如何设置默认页路径(如 404 页)
在上文的路由列表 Routes
中,我们可以加入一个 catch all
的默认页面,比如用来作 404 页面。
我们只要在最后加入 path
为 *
的一个路径,意为匹配所有路径,即可
function App() {
return <BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
}
// 用来作为 404 页面的组件
const NotFound = () => {
return <div>你来到了没有知识的荒原</div>
}
6.Link
- 作用: 修改URL,且不发送网络请求(路由链接)。
- 注意: 外侧需要用
<BrowserRouter>
或<HashRouter>
包裹。
import { Link } from "react-router-dom";
function Test() {
return (
<div>
<Link to="/路径">按钮</Link>
</div>
);
}
7.NavLink
作用: 与<Link>
组件类似,且可实现导航的“高亮”效果。
// 注意: NavLink默认类名是active,下面是指定自定义的class
//自定义样式
// 这里的isActive是个boolean值,如果你激活了对应路由就会返回true
<NavLink
to="login"
className={({ isActive }) => {
console.log('home', isActive)
return isActive ? 'list-group-item myActive' : 'list-group-item'
}}
>login</NavLink>
/*
默认情况下,当Home的子组件匹配成功,Home的导航也会高亮,
当NavLink上添加了end属性后,若Home的子组件匹配成功,则Home的导航没有高亮效果。
*/
<NavLink to="home" end >home</NavLink>
我们可以把这个逻辑抽离出来
function computeClassName({isActive}){
return isActive?"list-group-item myActive":"list-group-item";
}
<NavLink className={computeClassName} to="/about">About</NavLink>
<NavLink className={computeClassName} to="/home">Home</NavLink>
8.Navigate
相当于5版本的Redirect,对于我来说Redirect语义化会更好的
作用:用于重定向。当其他路由都匹配不到时,显示默认的路由。
replace
属性用于控制跳转模式(push 或 replace,默认是push)。
我们访问 http://localhost:3000/home 时,展示Home组件; 访问 http://localhost:3000/about 时,展示About组件。 但访问 http://localhost:3000/ 时,既不展示Home组件,也不展示About组件。 现在,我们使用Redirect Navigate组件实现重定向:
<Route path="/" element={<Navigate to="/about"/>}></Route>
此时访问 http://localhost:3000/ 将会重定向至 http://localhost:3000/about ,即默认展示About组件。
import React from 'react'
import About from "./pages/About";
import Home from "./pages/Home";
import {Route,Routes,Navigate} from "react-router-dom";
<Routes>
<Route path="/about" element={<About/>}></Route>
<Route path="/home" element={<Home/>}></Route>
<Route path="/" element={<Navigate to="/about"/>}></Route>
</Routes>
跳转模式的使用:
import React,{useState} from 'react'
import {Navigate} from 'react-router-dom'
export default function Home() {
const [sum,setSum] = useState(1)
return (
<div>
<h3>我是Home的内容</h3>
{/* 根据sum的值决定是否切换视图 */}
{sum === 1 ? <h4>sum的值为{sum}</h4> : <Navigate to="/about" replace={true}/>}
<button onClick={()=>setSum(2)}>点我将sum变为2</button>
</div>
)
}
9.使用useRoutes注册路由
9.1 第一次改进
useRoutes()
- 作用:根据路由表,动态创建
<Routes>
和<Route>
。
import React from 'react'
import {NavLink,Navigate,useRoutes} from "react-router-dom";
import About from "./pages/About";
import Home from "./pages/Home";
export default function App() {
const element = useRoutes([
{
path:"home",
element:<Home/>
},
{
path:"about",
element:<About/>
},
{
path:"/",
element:<Navigate to="/about"/>
},
])
return (
<div>
<NavLink to="/about">About</NavLink>
<NavLink to="/home">Home</NavLink>
<div className="content">
{element}
</div>
</div>
)
}
注意点:useRoutes([])
,useRoutes根据路由表生成对应的路由规则。
9.2 第二次改进
src文件夹下新建子文件夹:routes
,routes
下新建文件:index.js
路由表独立成js文件:src/routes/index.js
src/routes/index.js
import { Navigate } from "react-router-dom";
import About from "../pages/About";
import Home from "../pages/Home";
const routes = [
{
path:"/about",
element:<About/>
},
{
path:"/home",
element:<Home/>
},
{
path:"/",
element:<Navigate to="/about"/>
}
]
export default routes;
App.js
import React from 'react'
import {NavLink,useRoutes} from "react-router-dom";
import routes from "./routes";
export default function App() {
const element = useRoutes(routes);
return (
<div>
<NavLink to="/about">About</NavLink>
<NavLink to="/home">Home</NavLink>
<div className="content">
{element}
</div>
</div>
)
}
10.嵌套路由
效果:

路由结构如下:
react_staging
├─ public // 公共资源
├─ src // 源码文件夹
│ ├─ components
│ │ └─ MyNavLink
│ │ └─ index.jsx
│ ├─ pages
│ │ ├─ About
│ │ │ └─ index.jsx
│ │ └─ Home
│ │ ├─ News
│ │ │ └─ index.jsx
│ │ ├─ Message
│ │ │ └─ index.jsx
│ │ └─ index.lsx
│ ├─ routes
│ │ └─ index.js
│ ├─ App.jsx // App组件
│ └─ index.js // js入口文件
├─ .gitignore // 自动创建本地仓库
├─ package.json // 相关配置文件
├─ README.md // 项目说明文档
└─ yarn.lock
src/routes/index.js
嵌套的路由写在**children
**里 。
import { Navigate } from "react-router-dom";
import About from "../pages/About";
import Home from "../pages/Home";
import News from "../pages/Home/News";
import Message from "../pages/Home/Message";
const routes = [
{
path:"/about",
element:<About/>
},
{
path:"/home",
element:<Home/>,
children:[
{
path:"news",
element:<News/>
},
{
path:"message",
element:<Message/>
},
]
},
{
path:"/",
element:<Navigate to="/about"/>
}
]
export default routes;
src/App.jsx
import React from 'react'
import {NavLink,useRoutes} from 'react-router-dom'
import routes from './routes'
export default function App() {
//根据路由表生成对应的路由规则
const element = useRoutes(routes)
return (
<div>
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header"><h2>React Router Demo</h2></div>
</div>
</div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 路由链接 */}
<NavLink className="list-group-item" to="/about">About</NavLink>
<NavLink className="list-group-item" to="/home">Home</NavLink>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 注册路由 */}
{element}
</div>
</div>
</div>
</div>
</div>
)
}
src/pages/Home/index.jsx
<Outlet>
:当<Route>
产生嵌套时,渲染其对应的子路由。
路由链接中的 to
属性值,可以是
to="/home/news"
,即全路径(推荐这样写,不然直接看不知道是不是子路由)to="./news"
,即相对路径to="news"
import React from 'react';
import { NavLink,Outlet } from 'react-router-dom';
export default function Home() {
return (
<div>
<h2>Home组件内容</h2>
<div>
<ul className="nav nav-tabs">
<li>
{/* <NavLink to="/home/news" className="list-group-item">News</NavLink> */}
{/* <NavLink to="./news" className="list-group-item">News</NavLink> */}
{/* 由于Home路由组件匹配了/home,因此子路由可以写./news或news就可以拼上完整的/home/news路径 */}
<NavLink to="news" className="list-group-item">News</NavLink>
</li>
<li>
<NavLink to="/home/message" className="list-group-item">Message</NavLink>
</li>
</ul>
{/* 指定路由组件呈现的位置 */}
<Outlet/>
</div>
</div>
)
}
src/pages/Home/News/index.jsx
import React, { Component } from 'react'
export default class News extends Component {
render() {
return (
<ul>
<li>news001</li>
<li>news002</li>
<li>news003</li>
</ul>
)
}
}
src/pages/Home/Message/index.jsx
import React, { Component } from 'react'
export default class Message extends Component {
render() {
return (
<div>
<ul>
<li>
<a href="/message1">message001</a>
</li>
<li>
<a href="/message2">message002</a>
</li>
<li>
<a href="/message/3">message003</a>
</li>
</ul>
</div>
)
}
}
src/pages/About/index.jsx
import React, { Component } from 'react'
export default class About extends Component {
render() {
return (
<h3>我是About的内容</h3>
)
}
}
11.路由传递参数
11.1 传递 params 参数
需求描述:点击“消息1”,显示其id、title和content。

src/routes/index.js
import { Navigate } from "react-router-dom";
import About from "../pages/About";
import Home from "../pages/Home";
import News from "../pages/Home/News";
import Message from "../pages/Home/Message";
import Detail from "../pages/Home/Message/Detail"
const routes = [
{
path: '/about',
element: <About />
},
{
path: '/home',
element: <Home />,
children: [
{
path: 'news',
element: <News />
},
{
path: 'message',
element: <Message />,
children: [
{
path: 'detail/:id/:title/:content',
element: <Detail />
}
]
}
]
},
{
path: '/',
element: <Navigate to="/about" />
}
]
export default routes;
src/pages/Home/Message/index.jsx
(Message组件)
import React,{useState} from 'react'
import {Link,Outlet} from 'react-router-dom'
export default function Message() {
const [messages] = useState([
{id:'001',title:'消息1',content:'锄禾日当午'},
{id:'002',title:'消息2',content:'汗滴禾下土'},
{id:'003',title:'消息3',content:'谁知盘中餐'},
{id:'004',title:'消息4',content:'粒粒皆辛苦'}
])
return (
<div>
<ul>
{
messages.map((m)=>{
return (
// 路由链接
<li key={m.id}>
<Link to={`detail/${m.id}/${m.title}/${m.content}`}>{m.title}</Link>
</li>
)
})
}
</ul>
<hr />
{/* 指定路由组件的展示位置 */}
<Outlet />
</div>
)
}
src/pages/Home/Message/Detail/index.jsx
(Detail组件)
useParams()
:回当前匹配路由的params
参数,类似于5.x中的this.props.match.params
。
useMatch()
:返回当前匹配信息,相当于5.x中的路由组件的this.props.match
属性。
import React from 'react'
import {useParams,useMatch} from 'react-router-dom'
export default function Detail() {
const {id,title,content} = useParams()
// const x = useMatch('/home/message/detail/:id/:title/:content')
// console.log(x)
return (
<ul>
<li>消息编号:{id}</li>
<li>消息标题:{title}</li>
<li>消息内容:{content}</li>
</ul>
)
}
获取params参数有两种方式:
使用 useParams
const {id,title,content} = useParams();
使用 useMatch
const {params:{id,title,content}}= useMatch("/home/message/detail/:id/:title/:content");
11.2 传递 search 参数
演示的需求和上面params
参数一样,所以只修改关键部分
src/routes/index.js
const routes = [
{
path:"/home",
element:<Home/>,
children:[
{
path:"news",
element:<News/>
},
{
path:"message",
element:<Message/>,
children:[
{
path:"detail",
element:<Detail/>
}
]
},
]
},
]
export default routes;
src/pages/Home/Message/index.jsx
(Message组件)
return (
<div>
<ul>
{
messages.map(msgObj => {
return (
<li key={msgObj.id}>
<Link to={`detail?id=${msgObj.id}&title=${msgObj.title}&content=${msgObj.content}`}>{msgObj.title}</Link>
</li>
)
})
}
</ul>
<hr />
<Outlet />
</div>
)
src/pages/Home/Message/Detail/index.jsx
(Detail组件)
传递search参数有两种方式,一种是
useSearchParams()
,另一种是useLocation()
。
useSearchParams()
:用于读取和修改当前位置的 URL 中的查询字符串。返回一个包含两个值的数组,内容分别为:当前的seaech参数、更新search的函数。
useLocation()
:获取当前 location 信息,相当于5.x中的路由组件的location
属性。
使用useSearchParams
import { useSearchParams } from 'react-router-dom'
export default function Detail() {
const [search,setSearch] = useSearchParams();
const id = search.get("id");
const title = search.get("title");
const content = search.get("content");
return (
<ul>
<li>
<button onClick={()=>setSearch('id=008&title=哈哈&content=嘻嘻')}>点我更新一下收到的search参数</button>
</li>
<li>消息编号:{id}</li>
<li>消息标题:{title}</li>
<li>消息内容:{content}</li>
</ul>
)
}

点击按钮后

使用useLocation
记得下载安装qs:npm install --save qs
。
nodejs
官方说明querystring
这个模块即将被废弃,推荐我们使用qs
模块
import { useLocation } from 'react-router-dom'
import qs from "qs";
export default function Detail() {
const {search} = useLocation();
const {id,title,content} = qs.parse(search.slice(1));
return (
<ul>
<li>消息编号:{id}</li>
<li>消息标题:{title}</li>
<li>消息内容:{content}</li>
</ul>
)
}
11.3 传递 state 参数
演示的需求和上面一样,只修改关键部分
src/routes/index.js
const routes = [
{
path:"/home",
element:<Home/>,
children:[
{
path:"news",
element:<News/>
},
{
path:"message",
element:<Message/>,
children:[
{
path:"detail",
element:<Detail/>
}
]
},
]
},
]
export default routes;
src/pages/Home/Message/index.jsx
(Message组件)
return (
<div>
<ul>
{
messages.map(msgObj => {
return (
<li key={msgObj.id}>
<Link to="detail" state={{ id: msgObj.id, title: msgObj.title, content: msgObj.content }} >{msgObj.title}</Link>
</li>
)
})
}
</ul>
<hr />
<Outlet />
</div>
)
src/pages/Home/Message/Detail/index.jsx
(Detail组件)
import React from 'react'
import { useLocation } from 'react-router-dom'
export default function Detail() {
const {state:{id,title,content}} = useLocation();
return (
<ul>
<li>消息编号:{id}</li>
<li>消息标题:{title}</li>
<li>消息内容:{content}</li>
</ul>
)
}
刷新页面后对路由state参数的影响 在以前版本中,BrowserRouter没有任何影响,因为state保存在history对象中;HashRouter刷新后会导致路由state参数的丢失 但在V6版本中,HashRouter在页面刷新后不会导致路由state参数的丢失。
但是现在网站基本也没看过路径有个
#
,所以我们使用BrowserRouter
就行了。
12.编程式路由导航
案例还是和路由传递参数
一样,只是换了种方式传参数
12.1 传递params参数
src/pages/Home/Message/index.jsx
注意修改
src/routes/index.js
文件里Detail
组件的path
为detail/:id/:title/:content
,并修改src/pages/Home/Message/Detail/index.jsx
为上面路由传递params参数里的代码
import React,{useState} from 'react'
import { NavLink,Outlet,useNavigate } from 'react-router-dom'
export default function Message() {
const [messages] = useState([
{id:"001",title:"消息1",content:"窗前明月光"},
{id:"002",title:"消息2",content:"疑是地上霜"},
{id:"003",title:"消息3",content:"举头望明月"},
{id:"004",title:"消息4",content:"低头思故乡"}
])
const navigate = useNavigate();
function handleClick(msgObj){
const {id,title,content} = msgObj
navigate(`detail/${id}/${title}/${content}`,{replace:false})
}
return (
<div>
<ul>
{
messages.map(msgObj => {
return (
<li key={msgObj.id}>
<NavLink to={`detail/${msgObj.id}/${msgObj.title}/${msgObj.content}`} >{msgObj.title}</NavLink>
<button onClick={() => handleClick(msgObj)}>查看消息详情</button>
</li>
)
})
}
</ul>
<hr />
<Outlet/>
</div>
)
}
12.2 传递search参数
src/pages/Home/Message/index.jsx
注意修改
src/routes/index.js
文件里Detail
组件的path
为detail
,并修改src/pages/Home/Message/Detail/index.jsx
为上面路由传递search参数里的代码
import React, { useState } from 'react'
import { NavLink, Outlet, useNavigate } from 'react-router-dom'
export default function Message() {
const [messages] = useState([
{ id: "001", title: "消息1", content: "窗前明月光" },
{ id: "002", title: "消息2", content: "疑是地上霜" },
{ id: "003", title: "消息3", content: "举头望明月" },
{ id: "004", title: "消息4", content: "低头思故乡" }
])
const navigate = useNavigate();
function handleClick(msgObj) {
const { id, title, content } = msgObj
navigate(`detail?id=${id}&title=${title}&content=${content}`, { replace: false })
}
return (
<div>
<ul>
{
messages.map(msgObj => {
return (
<li key={msgObj.id}>
<NavLink to={`detail?id=${msgObj.id}&title=${msgObj.title}&content=${msgObj.content}`} >{msgObj.title}</NavLink>
<button onClick={() => handleClick(msgObj)}>查看消息详情</button>
</li>
)
})
}
</ul>
<hr />
<Outlet />
</div>
)
}
12.3 传递state参数
src/pages/Home/Message/index.jsx
注意修改
src/routes/index.js
文件里Detail
组件的path
为detail
,并修改src/pages/Home/Message/Detail/index.jsx
为上面路由传递state参数里的代码
import React, { useState } from 'react'
import { NavLink, Outlet, useNavigate } from 'react-router-dom'
export default function Message() {
const [messages] = useState([
{ id: "001", title: "消息1", content: "窗前明月光" },
{ id: "002", title: "消息2", content: "疑是地上霜" },
{ id: "003", title: "消息3", content: "举头望明月" },
{ id: "004", title: "消息4", content: "低头思故乡" }
])
const navigate = useNavigate();
function handleClick(msgObj) {
const { id, title, content } = msgObj
navigate("detail", {replace: false,state: {id,title,content}})
}
return (
<div>
<ul>
{
messages.map(msgObj => {
return (
<li key={msgObj.id}>
<NavLink to="detail" state={{ id: msgObj.id, title: msgObj.title, content: msgObj.content }} >{msgObj.title}</NavLink>
<button onClick={() => handleClick(msgObj)}>查看消息详情</button>
</li>
)
})
}
</ul>
<hr />
<Outlet />
</div>
)
}
12.4 useNavigate
5版本的时候,我们可以用 react-router-dom
里的 withRouter
函数 将一般组件包装为一个拥有 history
的组件,从而借助this.prosp.history
上的API对操作路由跳转、前进、后退。
this.prosp.history.goBack()
this.prosp.history.goForward()
this.prosp.history.go(1)
withRouter可以加工一般组件(即非路由组件),让一般组件具备路由组件所持有的API。但v6版本中已废除,可以直接用useNavigate实现前进、后退、刷新等操作。
import React from 'react';
import {useNavigate} from 'react-router-dom'
function App(props) {
const navigate = new useNavigate()
const back = ()=>{
navigate(-1)
}
const forward = ()=>{
navigate(1)
}
const go = ()=>{
navigate(2)
}
const refresh = () =>{
navigate(0)
}
return (
<div className="page-header">
<h2>React Router Demo</h2>
<button onClick={back}>回退</button>
<button onClick={forward}>前进</button>
<button onClick={go}>前进2次</button>
<button onClick={refresh}>刷新</button>
</div>
);
}
export default App;

08 【状态提升】
1.介绍
所谓 状态提升 就是将各个子组件的 公共state 提升到它们的父组件进行统一存储、处理(这就是所谓的”单一数据源“),负责setState
的函数传到下边的子级组件,然后再将父组件处理后的数据或函数props到各子组件中。
那么如果子组件 要 修改父组件的state该怎么办呢?我们的做法就是 将父组件中负责setState的函数,以props的形式传给子组件,然后子组件在需要改变state时调用即可。
实现方式
实现方式是 利用最近的共同的父级组件中,用props的方式传过去到两个子组件,props中传的是一个setState的方法,通过子组件触发props传过去的方法,进而调用父级组件的setState的方法,改变了父级组件的state,调用父级组件的render方法,进而同时改变了两个子级组件的render。
这是 两个有关连的同级组件的传值,因为react的单项数据流,所以不在两个组件中进行传值,而是提升到 最近的共同的父级组件中,改变父级的state,进而影响了两个子级组件的render。
官网介绍
通常,多个组件需要反映相同的变化数据,这时我们建议将共享状态提升到最近的共同父组件中去。
2.案例
先写一个温度输入组件:
class TemperatureInput extends React.Component {
state = {
temperature: ''
};
handleChange = (e) => {
this.setState({
temperature : e.target.value
})
};
render() {
return (
<fieldset>
<legend>输入{scaleNames[this.props.scale]}:</legend>
<input type="number" value={this.state.temperature} onChange={this.handleChange}
</fieldset>
)
}
}
这个组件就是一个普通的受控组件,有state
和props
以及处理函数。
我们在写另一个组件:
class Calculator extends React.Component {
render () {
return (
<div>
<TemperatureInput scale='c'/>
<TemperatureInput scale='f'/>
</div>
)
}
}
这个组件现在没有什么存在的价值,我们仅仅是给两个温度输入组件提供一个父组件,以便我们进行后续的状态提升。
现在我们看看网页的样子:

我们可以输入摄氏度和华氏度,但是我们现在想要让这两个温度保持一致,就是我们如果输入摄氏度,那么下面的华氏度可以自动算出来,如果我们输入华氏度,那么摄氏度就可以自动算出来。
那么我们按照现在这种结构的话,是非常难以实现的,因为我们知道这两个组件之间没有任何关系,它们之间是不知道对方的存在,所以我们需要把它们的状态进行提升,提升到它们的父组件当中。
那我们看看如何做修改,首先把子组件(温度输入组件)的状态(state)全部删除,看看是什么样子:
class TemperatureInput extends React.Component {
handleChange = (e) => {
};
render() {
return (
<fieldset>
<legend>输入{scaleNames[this.props.scale]}:</legend>
<input type="number" value={this.props.temperature} onChange={this.handleChange}/>
</fieldset>
)
}
}
可以看到所有与state
有关的东西全部删掉了,然后input
的value
也变成了props
,通过父组件传入。那么现在这个温度输入组件其实就是一个受控组件了,仔细回忆一下我们之前讲的受控组件,看看是不是这样意思?
我们通常会在受控组件发生改变的时候传入一个onChange
函数来改变受控组件的状态,那么我们这里也是一样,我们通过给 温度输入组件 传入某个函数来让 温度输入组件 中的input
发生变化的时候调用,当然这个函数我们可以随意命名,假如我们这里叫做onTemperatureChange
函数,那么我们继续修改子组件:
class TemperatureInput extends React.Component {
handleChange = (e) => {
this.props.onTemperatureChange(e.target.value);
};
render() {
return (
<fieldset>
<legend>输入{scaleNames[this.props.scale]}:</legend>
<input type="number" value={this.props.temperature} onChange={this.handleChange}/>
</fieldset>
)
}
}
好了,我们的子组件差不多就是这样了,当然我们可以省略那个handleChange
函数,因为可以看到这个函数就是调用了一下那个props
里的函数,所以我们完全把input
的onChange
这么写 <input type="number" value={this.props.temperature} onChange={this.props.onTemperatureChange}/>
这么写的话注意onTemperatrueChange
函数的参数是那个事件,而不是我这里写的e.target.value
。
再看看我们的父组件如何修改,我们首先补上state
,以及子组件对应的onChange
处理方法,以及子组件的值。写好之后大概是这个样子:
class Calculator extends React.Component {
state = {
celsius: '',
fahrenheit: ''
};
onCelsiusChange = (value) => {
this.setState({
celsius: value,
fahrenheit: tryConvert(value, toFahrenheit)
});
};
onFahrenheitChange = (value) => {
this.setState({
celsius: tryConvert(value, toCelsius),
fahrenheit: value
});
};
render() {
return (
<div>
<TemperatureInput scale='c' temperature={this.state.celsius}
onTemperatureChange={this.onCelsiusChange}/>
<TemperatureInput scale='f' temperature={this.state.fahrenheit}
onTemperatureChange={this.onFahrenheitChange}/>
</div>
)
}
}
这里我们省略的摄氏度与华氏度的转换函数,比较简单,大家自行搜索方法。
09 【组合组件】(类似vue插槽)
1.包含关系
有些组件无法提前知晓它们子组件的具体内容。在 Sidebar
(侧边栏)和 Dialog
(对话框)等展现通用容器(box)的组件中特别容易遇到这种情况。
我们建议这些组件使用一个特殊的 children
prop 来将他们的子组件传递到渲染结果中:
组件标签里面包含的子元素会通过
props.children
传递进来。
function One(props) {
return (
<div>{props.children}</div>
//特殊的children props
);
}
function Two(props) {
return (
//这使别的组件可以通过JSX嵌套,来将任意组件作为子组件来传递给他们
<One>
<div>Hello</div>
<div>World</div>
</One>
);
}

2.特例关系问题
有些时候,我们会把一些组件看作是其他组件的特殊实例,比如 WelcomeDialog
可以说是 Dialog
的特殊实例。
在 React 中,我们也可以通过组合来实现这一点。“特殊”组件可以通过 props 定制并渲染“一般”组件:
.FancyBorder {
padding: 10px 10px;
border: 10px solid;
}
.FancyBorder-blue {
border-color: blue;
}
.Dialog-title {
margin: 0;
font-family: sans-serif;
}
.Dialog-message {
font-size: larger;
}
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
</FancyBorder>
);
}
function WelcomeDialog() {
return (
<Dialog
title="Welcome"
message="Thank you for visiting our spacecraft!" />
);
}
组合也同样适用于以 class 形式定义的组件。
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
{props.children}
</FancyBorder>
);
}
class SignUpDialog extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSignUp = this.handleSignUp.bind(this);
this.state = {login: ''};
}
render() {
return (
<Dialog title="Mars Exploration Program"
message="How should we refer to you?">
<input value={this.state.login}
onChange={this.handleChange} />
<button onClick={this.handleSignUp}>
Sign Me Up!
</button>
</Dialog>
);
}
handleChange(e) {
this.setState({login: e.target.value});
}
handleSignUp() {
alert(`Welcome aboard, ${this.state.login}!`);
}
}