路由(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和跳转Linkimport {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;//最新的typeif (newType !== this.state.type) {// 不一样才请求数据,不然一直在请求,因为setState会频繁触发Updataaxios.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的没有样式
