1.SPA
按照以往的开发模式,许多页面可能要写需要HTML文件,我们把这种称为
MPA也叫多页面应用反之我们还有另一种应用,叫做单页应用程序
SPA(single page application)
- 整个应用只有一个完整的页面
- 点击页面中的链接不会刷新页面,只会做页面的局部更新
- 数据都需要通过ajax请求获取, 并在前端异步展现。
2. 路由是什么?
一个路由就是一个映射关系(key:value)
key为路径, value可能是
function或component
后端路由:
- 理解: value是function, 用来处理客户端提交的请求。
- 注册路由: router.get(path, function(req, res))
- 工作过程:当node接收到一个请求时, 根据请求路径和方法找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
前端路由:
- 浏览器端路由,value是component,用于展示页面内容
- 注册路由:
<Route path="/test" component={Test}> - 工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件
React路由react-router-dom
- react常用的一个插件,专门给 web 人员使用的库用来实现一个SPA应用
3.相关API
<BrowserRouter><HashRouter><Route><Redirect><Link><NavLink><Switch>
其他
- history对象
- match对象
- withRouter函数
4.基本使用
首先我们要明确好页面的布局 ,分好导航区、展示区
要引入 react-router-dom 库,暴露一些属性 Link、BrowserRouter...
import { Link, BrowserRouter, Route } from 'react-router-dom'
导航区的 a 标签改为 Link 标签
<Link className="list-group-item" to="/about">About</Link>
同时我们需要用 Route 标签,来进行路径的匹配,从而实现不同路径的组件切换
<Route path="/about" component={About}></Route>
<Route path="/home" component={Home}></Route>
上面的两组路由需要添加对应的路由器才能工作
如果我们在 Link 和 Route 中分别用路由器管理,那这样是实现不了的
只有在一个路由器的管理下才能进行页面的跳转工作。
因此我们也可以在 Link 和 Route 标签的外层标签采用 BrowserRouter 包裹
最方便的做法是根目录 index.js文件中,将整个 App 组件标签采用 BrowserRouter 标签去包裹
// index.js
<BrowserRouter>
< App />
</BrowserRouter>
5. 路由组件和一般组件
之前组件 Home 和组件 About 当成是一般组件来使用,我们将它们写在了 src 目录下的 components 文件夹下
但是我们又会发现它和普通的组件又有点不同
对于普通组件而言,我们在引入它们的时候我们是通过标签的形式来引用的
但是在上面我们可以看到,我们把它当作路由来引用时,我们是通过
{Home}来引用的所以组件实际分为普通组件和路由组件,之间的区别如下
1.写法不同: 一般组件:<Demo/> 路由组件:<Route path="/demo" component={Demo}/> 2.存放位置不同: 一般组件:components 路由组件:pages 3.接收到的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"
6.NavLink 标签
NavLink 标签是和 Link 标签作用相同的,但是它又比 Link 更加强大
在之前案例中点击并没有高亮效果
这时我们其实只需要对应设置active类名的样式即可
因为当我们选中某个 NavLink 标签时,就会自动给当前标签添加
active我们也可以通过
activeClassName="abc"将激活时类名更改为abc
7. NavLink 封装
上面的NavLink 标签,我们需要重复书写
activeClassName和样式名称,我们可以来封装下NavLink减少冗余
首先我们需要新建一个 MyNavLink 组件,return 一个结构
<NavLink activeClassName="abc" className="list-group-item" {...this.props}/>
接下来我们在调用时,直接写
<MyNavLink to="/home" chilren="About">home</MyNavLink>
标签体内容是一个特殊的标签属性
通过this.props.children可以获取标签体内容,标签体显示可以通过标签属性chilren="xxx"
8.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 组件进行包裹,匹配到对应组件停止匹配
Switch可以提高路由匹配效率(单一匹配),
<Switch>
<Route path="/home" component={Home}></Route>
<Route path="/about" component={About}></Route>
<Route path="/about" component={About}></Route>
</Switch>
在使用 Switch 时,我们需要先从 react-router-dom 中暴露出 Switch 组件
9.解决二级路由样式丢失的问题
当我们将路径改写成
path="/yunmu/about"这样的形式时我们会发现当我们强制刷新页面的时候,页面的 CSS 样式消失了
这是因为我们在引入样式文件时,采取的是相对路径
当我们使用二级路由的时候,会使得请求的路径发生改变,浏览器会向
localhost:3000/yunmu下请求 css 样式资源,这并不是我们想要的,因为我们的样式存放于公共文件下的 CSS 文件夹中
解决方法有三种:
public/index.html中 引入样式时不写./写/(常用)public/index.html中 引入样式时不写./写%PUBLIC_URL%(常用)- 使用HashRouter
10. 路由的严格匹配和模糊匹配
路由的匹配有两种形式,一种是精准匹配一种是模糊匹配,React 中默认开启的是模糊匹配
模糊匹配要去【输入的路径】必须包含要【匹配的路径】,且顺序要一致
严格严格匹配【输入的路径】必须包含要【匹配的路径】一致
开启严格匹配:
<Route exact={true} path="/about" component={About}/>严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
我们展示一个模糊匹配的例子
这个标签匹配的路由,我们可以拆分成 home a b
<MyNavLink to="/home/a/b" >Home</MyNavLink>
这个将会根据上面的先后顺序匹配路由,此时匹配的是home,可以匹配成功
<Route path="/home"component={Home}/>
当匹配的路由改成下面这样时,就会失败。它会按照第一个来匹配,如果第一个没有匹配上,那就会失败,这里的 a 和 home 没有匹配上,很显然会失败
<Route path="/a" component={Home}/>
当我们开启了精准匹配后,就我们的第一种匹配就不会成功,因为精准匹配需要的是完全一样的值,开启精准匹配采用的是 exact 来实现
Route exact={true} path="/home" component={Home}/>
11.重定向路由
我们有时会发现我们需要去点击一个按钮才去匹配组件
但我们想要页面一加载就默认匹配到一个组件(页面一加载就会去匹配路由)
这个时候我们就需要时候 Redirecrt 进行默认匹配实现此需求
一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about"/>
</Switch>
import { Redirect } from 'react-router-dom';
class Login extends Component {
render() {
if (this.state.isLogin) {
return <Redirect to="/"/>
}
}
}
12.嵌套路由
我们需要在一个路由组件中添加两个组件,一个是头部,一个是内容区
我们将我们的嵌套内容写在相应的组件里面,这个是在 Home 组件的 return jsx的 内容
注册子路由时要写上父路由的path值
路由的匹配是按照注册路由的顺序进行的,我们是在Home组件注册的,所以匹配时会先去找Home组件,因为时模糊匹配所以会匹配成功
在 Home 组件里面去匹配相应的路由,从而找到 /home/news 进行匹配,因此找到 News 组件,进行匹配渲染
如果开启精确匹配的话,第一步的
/home/news匹配/home就会卡住不动,就不会往Home组件里面去渲染嵌套的内容了
<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>
在这里我们需要使用嵌套路由的方式,才能完成匹配
换一种方式书写嵌套路由
function News(props) {
return (
<div>
<div>
<Link to={`${props.match.url}/company`}>公司新闻</Link>
<Link to={`${props.match.url}/industry`}>行业新闻</Link>
</div>
<div>
<Route path={`${props.match.path}/company`} component={CompanyNews} />
<Route path={`${props.match.path}/industry`} component={IndustryNews}/>
</div>
</div>
);
}
function CompanyNews() {
return <div>公司新闻</div>
}
function IndustryNews() {
return <div>行业新闻</div>
}
13.向路由组件传递参数
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编码字符串,需要借助querystring解析
import url from 'url';
// 换一种方式解析search参数为对象
const { query } = url.parse(this.props.location.search, true);
3.state参数
- 路由链接(携带参数):
<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link> - 注册路由(无需声明,正常注册即可):
<Route path="/demo/test" component={Test}/> - 接收参数:
this.props.location.state - 备注:刷新也可以保留住参数
14.React 路由跳转
1. push 与 replace 模式
默认情况下,开启的是 push 模式,当我们每次点击跳转时,都会向栈中压入一个新的地址,在点击返回时,可以返回到上一个打开的地址,
如果我们不需要返回上一个打开的地址,可以使用replace 模式,这种模式与 push 模式不同,它不会保留上次记录,也就是替换了新的栈顶
我们只需要在需要开启的链接上加上 replace 即可
Link replace to={{ pathname: '/home/message/detail', state: { id: msgObj.id, title: msgObj.title } }}>{msgObj.title}</Link>
15.编程式路由导航
我们可以采用绑定事件的方式实现路由的跳转,我们在按钮上绑定一个
onClick事件,当事件触发时,我们执行一个回调replaceShow借助this.prosp.history对象上的API对操作路由跳转、前进、后退
-this.prosp.history.push(path, state)
-this.prosp.history.replace(path, state)
-this.prosp.history.goBack()
-this.prosp.history.goForward()
-this.prosp.history.go()
replaceShow = (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 });
};
// push同理
back = () => {
this.props.history.goBack();
};
forward = () => {
this.props.history.goForward();
};
go = () => {
this.props.history.go(-2);
};
16.withRouter
withRouter可以加工一般组件,让一般组件具备路由组件所特有的API,比如history 对象
withRouter的返回值是一个新组件
我们需要对哪个组件包装就在哪个组件下引入
import React, { Component } from "react";
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 default withRouter(Header);
这样就能让一般组件获得路由组件所特有的 API
17.BrowserRouter 和 HashRouter 的区别
1.底层原理不一样:
BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。
HashRouter使用的是URL的哈希值。
2.path表现形式不一样
BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
HashRouter的路径包含#,例如:localhost:3000/#/demo/test
3.刷新后对路由state参数的影响
BrowserRouter没有任何影响,因为state保存在history对象中。
HashRouter刷新后会导致路由state参数的丢失!!!
4.备注:HashRouter可以用于解决一些路径错误相关的问题。