React 路由介绍(七)
React 官网链接:
接着上节 React ajax 发送请求(六)
前言
在我们实际开发的时候,React 应用其实是一个单页Web应用(single page web application,SPA),SPA应用的特点如下:
- 单页Web应用,整个应用只有一个完整的页面
- 点击页面中的链接 不会刷新整个页面,只会做页面的 局部更新
- 数据都需要通过 ajax 请求获取,并在前端异步展现
在原生的 html 中,跳转页面靠 标签来进行页面的跳转,而在 React 中靠 路由链接 实现组件的切换,从而实现页面的局部更新,所以需要编写 路由链接,这里会涉及一个概念 路由
路由
1. 什么是路由
- 一个路由就是一个映射关系 (key:value)
- key为路径,value可能是 function 或 component
2. 路由分类
后端路由:
-
理解: value 是 function,用来处理客户端提交的请求
-
注册路由:
router.get(path, function(req, res))
-
工作过程:当 node 接收到一个请求时,根据请求路径找到匹配的路由,调用路由中的函数来处理请求,返回响应数据
前端路由:
-
浏览器端路由,value 是 component,用于展示页面内容
-
注册路由
<Route path="/test" component={Test}>
-
工作过程:当浏览器的path变为 /test 时,当前路由组件就会变为 Test组件
react-router-dom
在 React 实现 SPA 应用的时候就不得不介绍一个插件库 react-router-dom,基于react的项目基本都会用到此库
1、安装
因为其是一个插件库,直接安装这个插件即可, 如果没有环境,请查看 node 介绍、安装、升级(node npm)
npm i react-router-dom
2、基本知识介绍
2.1 模糊匹配 与 精准匹配
- 模糊匹配: 路由匹配时,以 符合的 path 开头就算匹配成功,匹配成功就加载对应组件
- 精准匹配: 路由匹配时,path要完全符合,就算匹配成功
example:
1)定义了一个链接 , 去往的 path 定义为 /home/a
2)声明了一个路由 , 指定path 为 /home 时,去往 home 组件
- 如果是模糊匹配:点击该链接,会切换至 home 组件(前缀相同)
- 如果是精准匹配:点击该链接,不会切换至 home 组件(路径需要完全一样),如果又声明了一个路由 指定path 为 /home/a 时,去往 a 组件 , 点击该链接,会切换至 a组件
2.2 路由规则(栈)
因为是SPA应用,仅仅涉及一个页面,通过路由来实现局部刷新,声明了很多路由Route与对应的路由跳转Link
我们在浏览器操作时候,可通过点击 路由跳转Link 实现路由跳转,也可点击 回退按钮 / 前进按钮 实现路由切换,路由的具体规则如下:
路由总体来说是一个栈的结构,点击新路由即将该路由入栈,入栈有两种方式:
- 追加(PUSH):即不移除当前栈顶,下面讲解的Route 路由切换默认是这种方式
- 替代(REPLACE):即将新入栈的路由替代当前栈顶
两种方式直观的区间就是:
- 追加(PUSH):点击多个链接使路由入栈,然后 点击 回退按钮 / 前进按钮 可实现路由切换。
- 替代(REPLACE):点击多个链接使路由入栈,然后 点击 回退按钮 / 前进按钮 ,之前入栈的路由不会显示
图示:
追加 的方式:先点击了 About 使 /about 路由 入栈,后点击了 Home 使 /home 路由入栈,那么在 /about 可以点击 前进按钮 进入到 /home 路由,在 /home 可以点击 后退按钮 进入到 /about路由,正常进行路由切换
替换 的方式:先点击了 About 使 /about 路由 入栈,后点击了 Home 使 /home 路由入栈,那么在 /about 可以点击 前进/后退按钮 只会停留在 /about 路由,同理,在 /home 可以点击 前进/后退按钮 只会停留在 /home路由
2.3 路由组件与普通组件
路由组件与普通组件的实现基本相同,都是编写jsx文件,但是在具体的使用有一些差距:
example:
- 普通组件的使用:(<App/>)
//引入App 组件
import App from './App'
ReactDOM.render(
<App/> // 使用 普通组件
document.getElementById('root')
)
- 路由组件的使用:(Home)
import Home from './Home' // 引用Home组件
return (
<div>
<h1>react路由基本使用</h1>
<HashRouter>
<Link to="/home">评论</Link>
// 使用路由组件 Home
<Route path="/home" component={Home} />
</Switch>
</HashRouter>
</div>
)
说明:
普通组件与路由组件的一个很重要的区别在于 props (即传入的参数)的不同:
- 普通组件:若在使用的时候,不传入参数,那props即为空
- 路由组件:即使在使用的时候,不传入参数,props也会有相应的东西(具体字段后面讲 路由组件传递参数 的时候说明)
如果想要普通组件 变成 路由组件 ,具备props,可以在 组件 export 的时候,利用 <withRouter> 包裹即可
import React, { Component } from 'react'
// 导入 withRouter
import {withRouter} from 'react-router-dom'
// 本来式一个普通组件
class Header extends Component {
back = ()=>{
this.props.history.goBack()
}
forward = ()=>{
this.props.history.goForward()
}
go = ()=>{
this.props.history.go(-2)
}
render() {
console.log('Header组件收到的props是',this.props);
return (
<div className="page-header">
<h2>React Router Demo</h2>
<button onClick={this.back}>回退</button>
<button onClick={this.forward}>前进</button>
<button onClick={this.go}>go</button>
</div>
)
}
}
// 用 withRouter 包裹进行 export 即可成为 路由组件
export default withRouter(Header)
3、API介绍与使用
react-router-dom 中内置了很多组件,如下:
- Router 组件
- <BrowserRouter>
- <HashRouter>
- Route 组件
- <Route>
- Link 组件
- <Link>
- <NavLink>
- Switch 组件
- <Switch>
- Redirect 组件
- <Redirect>
下面一一介绍一下
3.1 Router 组件 (其用于整个应用,一般一个 React 应用仅需使用一次)
常见的有两种 :
- <BrowserRouter>
- <HashRouter>
组件一般放在最顶层所有组件之外,这样能确保内部组件使用 Link 做路由跳转时不出错
这两个组件的底层区别在于使用原理不同,直观区别是其路径前缀会不会有 #
号出现,常用的话,一般 BrowserRouter
使用的多一些
BrowserRouter
:使用 H5 的 history.pushState() API 实现,原理即监听 window 的 popstate 事件,路径不会带#
号,例如 :- 启动路径为 localhost:3000 ,若 path 为 /about ,则浏览器地址为 localhost:3000/about
HashRouter
:使用 URL 的 hash 实现,原理即监听 window 的 hashchange 事件,路径会带#
号,例如 :- 启动路径为 localhost:3000 ,若 path 为 /about ,则浏览器地址为 localhost:3000/#/about
使用举例:(index.js )
//引入react核心库
import React from 'react'
//引入ReactDOM
import ReactDOM from 'react-dom'
// BrowserRouter
import {BrowserRouter , HashRouter} from 'react-router-dom'
//引入App
import App from './App'
ReactDOM.render(
<BrowserRouter>
<App/>
</BrowserRouter>,
document.getElementById('root')
)
// 或者
ReactDOM.render(
<HashRouter>
<App/>
</HashRouter>,
document.getElementById('root')
)
图示:
BrowserRouter:
HashRouter:
3.2 Link 组件 :编写路由链接,指定路由导航
常见的有两种 :
- <Link > 可配置字段如下:(常用的已加粗显示)
- to=“”:具体的path
- replace:将路由规则改为 替代模式(默认是追加),取值为 boolean 或者 undefined
- innerRef:引用的标签,类似a标签的ref,取值为 React.Ref<HTMLAnchorElement> 或者 undefined;
- <NavLink> 可配置字段如下:(常用的已加粗显示)
- to:具体的path
- activeClassName:高亮样式类名,取值为 string 或者 undefined
- activeStyle:高亮的样式,取值为 React.CSSProperties 或者 undefined
- exact:是否精准匹配,取值为 boolean 或者 undefined
- strict:是否开启严格模式 ,取值为 boolean 或者 undefined
- isActive:当前导航是否是这个 取值为 Location 或者 boolean
- location:当前导航的具体位置,取值为 H.Location
或者 undefined - className:如果当前导航是这个,为类名称,取值为 string 或者 undefined;
- style:取值为 React.CSSProperties 或者 undefined
- sensitive:取值为 boolean 或者 undefined;
共同点:
- 两个组件中的 to 属性都是会渲染成 <a> 的 href 属性
区别:
- NavLink是一个特殊的 Link 组件,其有一些额外的字段可配置,可用于指定当前导航高亮
使用举例:(App.jsx )
import React, { Component } from 'react'
import {Link, NavLink , 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">
{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
<Link className="list-group-item" to="/about">About</Link>
<Link className="list-group-item" to="/home">Home</Link>
{/* 在React中靠路由链接实现切换组件--编写路由链接 , hhhh 是自己定义的点击的高亮样式 */}
<NavLink activeClassName="hhhh" className="list-group-item" to="/about">About</NavLink>
<NavLink activeClassName="hhhh" className="list-group-item" to="/home">Home</NavLink>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 注册路由 /about 对应写的 About 组件,/home 对应写的 Home 组件*/}
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
</div>
</div>
</div>
</div>
</div>
)
}
}
说明:(定义了两个 导航栏 分别是 About,Home)
- 如果用 Link , 点击 About / Home 切换时,切换成的当前导航并不会显示具体样式
- 如果用 NavLink, 点击 About / Home 切换时,切换成的当前导航会显示具体样式(这里即显示的是hhh样式)
3.3 Route 组件:路径的具体匹配规则,即path与component的对应关系
这个组件主要定义具体的路由匹配规则,
- <Route > 可配置字段如下:(常用的已加粗显示)
- path:具体的path,与 Link 组件的 to属性值对应, 取值为 Path 或者 Path[] 或者 undefined
- component:定义的组件,取值为 React.ComponentType<RouteComponentProps<any>> 或者 React.ComponentType<any> 或者 undefined
- exact:是否开启精准匹配,取值为 boolean 或者 undefined
- location: 取值为 H.Location 或者 undefined;
- render: 取值为 ((props: RouteComponentProps) => React.ReactNode) 或者 undefined;
- children: 取值为 ((props: RouteChildrenProps) => React.ReactNode) 或者 React.ReactNode 或者 undefined;
- sensitive: 取值为 boolean 或者 undefined
- strict: 取值为 boolean 或者 undefined
使用举例:(也可参照 Link 组件中的样例)
// path:link组件中to的属性值
<Route path="/xx/xx" component={组件}></Route>
模糊匹配 与 精准匹配(默认是模糊匹配)
- 模糊匹配 只要 link 组件中 to 的属性值,以 path 开头就算匹配成功,匹配成功就加载对应组件
- 精准匹配 需要 link 组件中 to 的属性值 与 path 完全相同才会匹配
example:
// 默认模糊匹配,这样的话,点击Home会切换至MyHome组件
<NavLink to="/home/a/b">Home</NavLink>
<Route exact path="/home" component={MyHome}/>
// exact 确定为精准匹配,这样的话,点击Home不会切换至MyHome组件
<NavLink to="/home/a/b">Home</NavLink>
<Route exact path="/home" exact component={MyHome}/>
3.4 Switch组件:包裹多个 Route ,根据路由规则进行匹配
<Switch>组件可 包裹多个Route
组件,对to的属性值进行匹配,但无论有多少个 Route 的路由规则匹配成功,都只会渲染第一个匹配的组件
example:(App.jsx)
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 className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
<NavLink to="/about">About</NavLink>
<NavLink to="/home">Home</NavLink>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 注册路由 */}
<Switch>
<Route path="/about" component={About}/>
// /home会匹配为Home组件,而不是切换为下面的 Test 组件
<Route path="/home" component={Home}/>
<Route path="/home" component={Test}/>
// 这里可以不设置path属性,将404页对应的组件放在最后位置来设置404页面
<Route component={Page404} />
</Switch>
</div>
</div>
</div>
</div>
</div>
)
}
}
3.5 Redirect组件:路由默认跳转组件的设置
<Redirect> 用来设置默认跳转路由
example:
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
<NavLink to="/about">About</NavLink>
<NavLink to="/home">Home</NavLink>
<NavLink to="/hshshhs">Home</NavLink>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 注册路由 */}
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about"/> // hshshhs会匹配为about,跳转对应的About组件,下面的实现方式也可,任选其一
{/* <Route path="/" component={About} /> */}
{/* <Redirect from="/" to="/about" /> */}
</Switch>
</div>
</div>
</div>
</div>
4、路由组件传递参数
首先来看下 路由组件 的默认props,例如在上面的路由组件 About 组件中打印出传过来的props(如果是普通组件)
export default class About extends Component {
render() {
console.log(this.props)
return (
<h3>我是About的内容</h3>
)
}
}
log 图示:
说明:(比较常用的字段如下)
- history
- action:这个路由规则,如果是追加就是 PUSH ,替换就是REPLACE
- go:路由跳转(等同于浏览器的前进/回退按钮),大小n为步数,n为正数即前进,负数即后退
- goForward:等同于浏览器的前进,前进一步
- goBack:等同于浏览器的回退,/回退一步
- location:与下面的location字段相同
- push:追加式增加路由
- replace:替换式增加路由
- location
- pathname:对应 Link 组件中 to 属性 的 pathname
- state:传参以 state 方式传参的接收key
- search:传参以 search 方式传参的接收key
- match
- isExact:是否是精准匹配得到的
- params:参数以 params 方式传参的接收key
- path:Route 组件的path 值
- url:具体的path,如果携带参数,显示具体的参数值
根据上面的介绍,路由组件传递参数有三种方式:
- params 参数
- search 参数
- state 参数
4.1 params 参数
使用方式:
- 在 Link 组件中的 to 属性值 直接在路径后面接具体的参数**(/参数一/参数二)**
- 在 Route 组件中的 path 属性值进行对应,进行声明接收
- 传递的参数在 this.props.match 中存储,对应的 key 为 params,value 为 {参数一:值一,参数二:值二},直接取值即可
定义与传递参数:
msgObj = {id:'01',title:'消息1'}
{/* 向路由组件传递params参数 */}
<Link className="list-group-item" to={`/about/${msgObj.id}/${msgObj.title}`}>About</Link>
{/* 声明接收params参数 */}
<Route path="/about/:id/:title" component={About}/>
接收参数:
export default class Detail extends Component {
render() {
console.log(this.props);
// 接收params参数
const {id,title} = this.props.match.params
return (
<ul>
<li>ID:{id}</li>
<li>TITLE:{title}</li>
</ul>
)
}
}
图示:
4.2 search参数
使用方式:
- 在 Link 组件中的 to 属性值 直接在路径后面以 ? 开头接具体的参数(?xx=xxx&xxx=xxxx)
- 在 Route 组件中正常进行注册路由
- 传递的参数在 this.props.location 中存储,对应的 key 为 search,value 为 ?xx=xxx&xxx=xxxx ,需要进行处理
定义与传递参数:
msgObj = {id:'01',title:'消息1'}
{/* 向路由组件传递search参数 */}
<Link className="list-group-item" to={`/about/?id=${msgObj.id}&title=${msgObj.title}`}>About</Link>
{/* search参数无需声明接收,正常注册路由即可 */}
<Route path="/about" component={About}/>
接收参数:
import qs from 'querystring'
export default class Detail extends Component {
render() {
console.log(this.props);
// 接收search参数
const {search} = this.props.location
const {id,title} = qs.parse(search.slice(1))
return (
<ul>
<li>ID:{id}</li>
<li>TITLE:{title}</li>
</ul>
)
}
}
图示:
4.3 state参数
使用方式:
- 在 Link 组件中的 to 属性值 ,指明 state 字段的值
- 在 Route 组件中正常进行注册路由
- 传递的参数在 this.props.location 中存储,对应的 key 为 state,value 为 传递的state字段的值
定义与传递参数:
msgObj = {id:'01',title:'消息1'}
{/* 向路由组件传递state参数 */}
<Link className="list-group-item" to={{pathname:'/about',state:{id:msgObj.id,title:msgObj.title}}}>About</Link>
{/* state参数无需声明接收,正常注册路由即可 */}
<Route path="/about" component={About}/>
接收参数:
export default class Detail extends Component {
render() {
console.log(this.props);
// 接收state参数
const {id,title} = this.props.location.state || {}
return (
<ul>
<li>ID:{id}</li>
<li>TITLE:{title}</li>
</ul>
)
}
}
图示:
5、编程式路由跳转
在第4节,讲解了默认props 的参数,其中几个方法可以实现路由跳转(不仅仅可通过借助浏览器的前进/回退方法)
- history
- go:路由跳转(等同于浏览器的前进/回退按钮),大小n为步数,n为正数即前进,负数即后退
- goForward:等同于浏览器的前进,前进一步
- goBack:等同于浏览器的回退,/回退一步
- push:追加式增加路由
- replace:替换式增加路由
example:
- 定义了 回退 按钮,使用 this.props.history.goBack() 函数实现回退
- 定义了 前进 按钮,使用 this.props.history.goForward() 函数实现回退
- 定义了 go 按钮,使用 this.props.history.go(n) 函数实现跳转,正数即前进,负数即回退
- 定义了 push查看 按钮,使用 this.props.history.push 函数实现增添路由
- 定义了 replace查看 按钮,使用 this.props.history.replace 函数实现增添路由
import React, { Component } from 'react'
import {Link,Route} from 'react-router-dom'
import Detail from './Detail'
export default class Message extends Component {
state = {
msgObj:{id:'02',title:'消息2'}
}
replaceShow = (id,title)=>{
//replace跳转+携带params参数
this.props.history.replace(`/home/message/detail/${id}/${title}`)
}
pushShow = (id,title)=>{
//push跳转+携带params参数
this.props.history.push(`/home/message/detail/${id}/${title}`)
}
back = ()=>{
this.props.history.goBack()
}
forward = ()=>{
this.props.history.goForward()
}
go = ()=>{
this.props.history.go(-2)
}
render() {
const {msgObj} = this.state
return (
<div>
{/* 向路由组件传递params参数 */}
<Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
<button onClick={()=> this.pushShow(msgObj.id,msgObj.title)}>push查看</button>
<button onClick={()=> this.replaceShow(msgObj.id,msgObj.title)}>replace查看</button>
<hr/>
{/* 声明接收params参数 */}
<Route path="/home/message/detail/:id/:title" component={Detail}/>
<button onClick={this.back}>回退</button>
<button onClick={this.forward}>前进</button>
<button onClick={this.go}>go</button>
</div>
)
}
}
总结
路由在 React 应用实现的过程中至关重要,需要很好的掌握,上面粗略的介绍了一些API与例子,大家在学习的过程中可以试试