路由(router)
React Router现在的版本是5,即V5 一切皆组件(从V4开始组件化)
版本3及之前没有组件的概念, 版本4和5 都遵循 just component - 万物皆组件,包括路由和重定向
1.后端路由(根据用户的请求返还不同的内容:api接口-其中koa是express的ES6版本)
2.前端路由:根据不同的url去切换组件 - 形成映射关系
2.0路由基本认识
目的:达到原生app的效果,没有页面跳转的闪烁感
网站:https://reacttraining.com/react-router/ 分为WEB和NATIVE(原生开发)
安装插件:npm install react-router-dom -S (有dom说明是用于web开发)
首先选择两种引入模式之一(在src/index.js入口js文件引入)
- 历史记录模式: 历史记录的api去切换组件的(缺点:上线后不能刷新,会丢失,需要另外配置,开发状态没事)
import {BrowserRouter as Router} from "react-router-dom";
//as后面接的简称> 上线nginx的话,一刷新就会出现404。(主要后端配置)vue官网/Vue Router/HTML5 History模式 这个文档 中 教你服务器配置
原理:利用h5的history pushstate这个api
- hash模式: #后面哈希值判断切换组件(跳转锚点:location.hash)
import {HashRouter as Router} from "react-router-dom";
//as后面接的简称
写法:
导入何种模式后:
//1.在src/index.js中的render函数的第一参数App根组件,外面套一个Router。这样写法就类似于插槽(即子节点)
<Router><App></Router>
//2.在src/app.js中先引入Route和跳转Link
import {Route,Link,NavLink} from "react-router-dom";
//然后在无状态组件app里面的className="app"的div下不直接写组件名了,而是写一个Route标签。这样会通过地址的path知道渲染那个组件,写法如下:
return (
<div className="App">
<Route path="组件相对于app.js的相对路径" component={组件名}/>
<Link to="与path相同">内容xxx</Link>
<NavLink to="与path相同">内容xxx</NavLink>
{/*一般多用NavLink*/}
</div>
)
// Link的to属性不仅可以传字符串,还可以传对象 to="path字符串"或to={{pathname:xxx}}
// Route还有两个属性render和children替代component;其值是回调函数 见下面的3.4
//Link与NavLink的区别:
NavLink在选中状态会<a href="与path相同" aria-current="page" class="active"></a>默认添加两个属性(针对这两个属性可以为选中状态添加样式等等);而Link没有这两个属性,相当于只能用于切换。
NavLink有一个属性<activeClassName>定义不同的点击class(没写这个属性的默认class是active),可以实现点击的样式无差异的问题,可以局部设置不同的点击状态。
内部原理:
路由标签包裹着App根组件,通过 context 向App的子孙组件去传值。
https://upload-images.jianshu.io/upload_images/1457831-b19e007758f57df7
https://upload-images.jianshu.io/upload_images/1457831-1b8e3c5ce88f7758
切记: 声明式导航的Link或NavLink必须是包含在Router组件内的,不然会报错。一般Router包裹在App的外层或者使用组件库用编程式导航。
2.1exact
例子:
精确匹配:不加exact,若path=”/url”,它不仅会渲染默认的组件,还会渲染url对应的组件。
加exact之后,就会达到精确匹配的目的。不会出现上述问题。但是默认组件的link没有样式属性
2.2重定向-Redriect
例子:
须事先定义默认组件的Route,若不定义,会报警告'Pubsub' is defined but never used no-unused-vars
若不加exact,404页面不可能显示出来(Switch只显示一个);加了exact只有/时重定向,而不存在的跳转404
原理:
import { Route, NavLink, Redirect, Switch } from "react-router-dom";
return (
<div className="app">
{
<Switch>
<Route path="/snapshot" component={Snapshot} />
<Route path="/pubsub" component={Pubsub} />
<Redirect from="/" to="/pubsub" exact />{/*目前的网址若匹配from,则跳转to*/}
<Route component={NotFound} />{/*404页面*/}
</Switch>
}
</div>
)
// Switch的目的每次只会匹配一项
// Redirect和404的页面顺序是一定的,其他没顺序要求
// 原理: 根据path一个一个比对排除,匹配到谁就渲染谁。
2.3嵌套路由
render(){
return(
<div>
Test1组件
<NavLink to="/test1/a">test1-a</NavLink>
<NavLink to="/test1/b">test1-b</NavLink>
<Switch>
<Route path="/test1/a" component={Test1A}/>
<Route path="/test1/b" render={(props)=>{
//1.直接渲染组件
<Test1B {...props} />
//2.或者在里面写Switch和Route-即嵌套路由
.......
}}/>
<Redirect from="test1" to="/test1/a" exact />
</Switch>
</div>
)
}
2.4权限路由(三目; if判断; &&符)
通过传过来的path和component由Route的属性的render来决定如何跳转
to不只是可以传字符串;还可以传对象(带参数);以及函数。
// PermissionRouting组件
render () {
let { path, component: ComponentFromProps } = this.props;
// 解构 再给属性命名
return (
<Route path={path} render={(props) => {
return sessionStorage.getItem('user') ? <ComponentFromProps {...props} /> :
// 这个render的props传过去是为了这个组件使用路由的三个属性
<Redirect to={{ pathname: "/login", state: { page: path } }} />
// 用Redirect是为了跳转到登录的时候同携带参数
// 不能用Route,不具备跳转的功能
}} />
)
}
//使用
<PermissionRouting path="/params" component={Params} />
3.有无路由的区别-props多了三个属性
@withRouter
用路由切换的组件的props多了三个属性:history、location、match
1. history | 适合做编程式导航(go等)和监听路由的变化 |
---|---|
listen | |
push | this.props.history.push(path-string, state) — 编程式导航 |
2. location | |
pathname | 主要使用location.pathname |
search | ?a=xxx&b=yyy 解析查询字符串import qs from 'querystring' qs.parse(this.props.location.search.slice(1))先去掉问号,再转成对象格式 |
state | 传递多个值,push方法的第二个参数(可传对象) — state 默认值是undefined |
3. match | |
params | 取路由的参数 — params 默认值 {} |
3.1 监听路由的变化 - this.props.history.listen方法
withRouter: 作用是让不是路由切换的组件也具有路由切换组件的三个属性。 如app.js: export default withRouter(App);// HOC
// this.routerListen = this.routerListen.bind(this);// 只绑定this
// this.routerListen.call(this);// 绑定this,并且调用
this.routerListen();//不用绑定this,this没有丢失。绑定到元素上this才是丢失了
this.changeTitle(this.props.location.pathname);//每次刷新直接调用,但是路由没有切换,所以routerListen监听不到
// 如果用到了this.setState,函数调用就放在componentDidMount;否则放到constructor也可以,最早执行的钩子函数。
routerListen () {
this.props.history.listen((location) => {
// console.log(location);
// locatin.pathname 会根据路由的变化而变化
this.changeTitle(location.pathname)
})
}
changeTitle (pathname) {
switch (pathname) {
case '/smartisan/':
case '/smartisan/home': document.title = '首页'; break;
// case '/smartisan/classify': document.title = '分类'; break;
case '/smartisan/shopcar': document.title = '购物车'; break;
case '/smartisan/mine': document.title = '我的'; break;
default:
if (pathname.includes('/smartisan/classify')) {
document.title = '分类';
}else{
// 404
}
}
}
// 监听路由变化,修改标题。
// 解决刷新 在constructor里又调用了一次函数 把 this.props.location.pathname传入
3.2 动态路由 - 参数的变化 this.props.match.params.参数params
注意:冒号后面的表示路由的参数params。如果后面接问号,表示这个参数传不传都能匹配到这个组件;若不加问号则表示这个参数是必传的,不然匹配不到这个组件。
// 父组件 冒号后面的表示路由参数params
<NavLink to="/params/user">user</NavLink>//声明式导航
<NavLink to="/params/goods">goods</NavLink>
<Route path="/params/:type?" component={List}></Route>
// List 子组件
constructor(props) {
super(props);
this.state = {
type: '',//储存列表的类型
list: []
};
this.getData();
}
componentDidUpdate () {
// 上午的history.listen监听的前一个状态的参数
// componentDidMount只能监听一次,因为这个钩子只执行一次
this.getData();
}
getData () {
let newType = this.props.match.params.type;//最新的type
if (newType !== this.state.type) {
// 不一样才请求数据,不然一直在请求,因为setState会频繁触发Updata
axios.get(`http://rap2.taobao.org:38080/app/mock/245256/example/${newType}`).then(res => {
if (res.data.code === 200) {
this.setState({
list: res.data.list,
type: newType// 更新type
})
}
})
}
}
3.3 通过state传值(地址不变化)
// params的默认值是{},而state的默认值是undefined。
if(!this.props.location.state) return;
如果不是通过编程式导航跳过来,是取不到state的下面的值,反而还会报错。
刷新时不会丢失的
总结: 传多个值用state
3.4 Route的另外两个属性-可用于权限路由或者嵌套路由
// render和children 若不传props则拿不到路由的三个属性
<Route path="/test" render={(props)=>{return <Test {...props} a={666}/>}}/>
<Route path="/test" children={(props)=>{return <Test {...props} a={666}/>}} />
// children属性的特点: 无论是否匹配都会渲染(没匹配则match为空;匹配了match有值)。但是用的相对较少
4.对于路由三个属性的获取方法
- 只有Route组件的component属性属性渲染的组件才有路由的3个属性。
- Route的render和children必须通过回调往下传递props
- 自Route一步一步往下传props
- 不想传,或者拿不到就去找withRouter
路由切换的组件才能拿到this.props中路由的3个属性。但是经过withRouter(一般最外层)返回的高阶组件可以是组件拥有路由的3个属性。
注意: 启动程序就用cmd
别用vs Code的终端启动项目(可能会出问题)、也不要用Git Bash Here启动项目后,端口不能释放,下次启动表示端口被占用
- 一用Redirect就报错
- NavLink的没有样式