路由(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文件引入)
  1. 历史记录模式: 历史记录的api去切换组件的(缺点:上线后不能刷新,会丢失,需要另外配置,开发状态没事)
    import {BrowserRouter as Router} from "react-router-dom";//as后面接的简称> 上线nginx的话,一刷新就会出现404。(主要后端配置)

    vue官网/Vue Router/HTML5 History模式 这个文档 中 教你服务器配置


原理:利用h5的history pushstate这个api

  1. hash模式: #后面哈希值判断切换组件(跳转锚点:location.hash)
    import {HashRouter as Router} from "react-router-dom";//as后面接的简称

写法:
  1. 导入何种模式后:
  2. //1.在src/index.js中的render函数的第一参数App根组件,外面套一个Router。这样写法就类似于插槽(即子节点)
  3. <Router><App></Router>
  1. //2.在src/app.js中先引入Route和跳转Link
  2. import {Route,Link,NavLink} from "react-router-dom";
  3. //然后在无状态组件app里面的className="app"的div下不直接写组件名了,而是写一个Route标签。这样会通过地址的path知道渲染那个组件,写法如下:
  4. return (
  5. <div className="App">
  6. <Route path="组件相对于app.js的相对路径" component={组件名}/>
  7. <Link to="与path相同">内容xxx</Link>
  8. <NavLink to="与path相同">内容xxx</NavLink>
  9. {/*一般多用NavLink*/}
  10. </div>
  11. )
  12. // Link的to属性不仅可以传字符串,还可以传对象 to="path字符串"或to={{pathname:xxx}}
  13. // Route还有两个属性render和children替代component;其值是回调函数 见下面的3.4
  14. //Link与NavLink的区别:
  15. NavLink在选中状态会<a href="与path相同" aria-current="page" class="active"></a>默认添加两个属性(针对这两个属性可以为选中状态添加样式等等);而Link没有这两个属性,相当于只能用于切换。
  16. NavLink有一个属性<activeClassName>定义不同的点击class(没写这个属性的默认classactive),可以实现点击的样式无差异的问题,可以局部设置不同的点击状态。

内部原理:

路由标签包裹着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对应的组件。

  1. exact之后,就会达到精确匹配的目的。不会出现上述问题。但是默认组件的link没有样式属性

2.2重定向-Redriect

例子:

须事先定义默认组件的Route,若不定义,会报警告'Pubsub' is defined but never used no-unused-vars

若不加exact,404页面不可能显示出来(Switch只显示一个);加了exact只有/时重定向,而不存在的跳转404

原理:

  1. import { Route, NavLink, Redirect, Switch } from "react-router-dom";
  2. return (
  3. <div className="app">
  4. {
  5. <Switch>
  6. <Route path="/snapshot" component={Snapshot} />
  7. <Route path="/pubsub" component={Pubsub} />
  8. <Redirect from="/" to="/pubsub" exact />{/*目前的网址若匹配from,则跳转to*/}
  9. <Route component={NotFound} />{/*404页面*/}
  10. </Switch>
  11. }
  12. </div>
  13. )
  14. // Switch的目的每次只会匹配一项
  15. // Redirect和404的页面顺序是一定的,其他没顺序要求
  16. // 原理: 根据path一个一个比对排除,匹配到谁就渲染谁。

2.3嵌套路由
  1. render(){
  2. return(
  3. <div>
  4. Test1组件
  5. <NavLink to="/test1/a">test1-a</NavLink>
  6. <NavLink to="/test1/b">test1-b</NavLink>
  7. <Switch>
  8. <Route path="/test1/a" component={Test1A}/>
  9. <Route path="/test1/b" render={(props)=>{
  10. //1.直接渲染组件
  11. <Test1B {...props} />
  12. //2.或者在里面写Switch和Route-即嵌套路由
  13. .......
  14. }}/>
  15. <Redirect from="test1" to="/test1/a" exact />
  16. </Switch>
  17. </div>
  18. )
  19. }

2.4权限路由(三目; if判断; &&符)

通过传过来的path和component由Route的属性的render来决定如何跳转

to不只是可以传字符串;还可以传对象(带参数);以及函数。

  1. // PermissionRouting组件
  2. render () {
  3. let { path, component: ComponentFromProps } = this.props;
  4. // 解构 再给属性命名
  5. return (
  6. <Route path={path} render={(props) => {
  7. return sessionStorage.getItem('user') ? <ComponentFromProps {...props} /> :
  8. // 这个render的props传过去是为了这个组件使用路由的三个属性
  9. <Redirect to={{ pathname: "/login", state: { page: path } }} />
  10. // 用Redirect是为了跳转到登录的时候同携带参数
  11. // 不能用Route,不具备跳转的功能
  12. }} />
  13. )
  14. }
  15. //使用
  16. <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

  1. // this.routerListen = this.routerListen.bind(this);// 只绑定this
  2. // this.routerListen.call(this);// 绑定this,并且调用
  3. this.routerListen();//不用绑定this,this没有丢失。绑定到元素上this才是丢失了
  4. this.changeTitle(this.props.location.pathname);//每次刷新直接调用,但是路由没有切换,所以routerListen监听不到
  5. // 如果用到了this.setState,函数调用就放在componentDidMount;否则放到constructor也可以,最早执行的钩子函数。
  6. routerListen () {
  7. this.props.history.listen((location) => {
  8. // console.log(location);
  9. // locatin.pathname 会根据路由的变化而变化
  10. this.changeTitle(location.pathname)
  11. })
  12. }
  13. changeTitle (pathname) {
  14. switch (pathname) {
  15. case '/smartisan/':
  16. case '/smartisan/home': document.title = '首页'; break;
  17. // case '/smartisan/classify': document.title = '分类'; break;
  18. case '/smartisan/shopcar': document.title = '购物车'; break;
  19. case '/smartisan/mine': document.title = '我的'; break;
  20. default:
  21. if (pathname.includes('/smartisan/classify')) {
  22. document.title = '分类';
  23. }else{
  24. // 404
  25. }
  26. }
  27. }
  28. // 监听路由变化,修改标题。
  29. // 解决刷新 在constructor里又调用了一次函数 把 this.props.location.pathname传入

3.2 动态路由 - 参数的变化 this.props.match.params.参数params

注意:冒号后面的表示路由的参数params。如果后面接问号,表示这个参数传不传都能匹配到这个组件;若不加问号则表示这个参数是必传的,不然匹配不到这个组件。

  1. // 父组件 冒号后面的表示路由参数params
  2. <NavLink to="/params/user">user</NavLink>//声明式导航
  3. <NavLink to="/params/goods">goods</NavLink>
  4. <Route path="/params/:type?" component={List}></Route>
  5. // List 子组件
  6. constructor(props) {
  7. super(props);
  8. this.state = {
  9. type: '',//储存列表的类型
  10. list: []
  11. };
  12. this.getData();
  13. }
  14. componentDidUpdate () {
  15. // 上午的history.listen监听的前一个状态的参数
  16. // componentDidMount只能监听一次,因为这个钩子只执行一次
  17. this.getData();
  18. }
  19. getData () {
  20. let newType = this.props.match.params.type;//最新的type
  21. if (newType !== this.state.type) {
  22. // 不一样才请求数据,不然一直在请求,因为setState会频繁触发Updata
  23. axios.get(`http://rap2.taobao.org:38080/app/mock/245256/example/${newType}`).then(res => {
  24. if (res.data.code === 200) {
  25. this.setState({
  26. list: res.data.list,
  27. type: newType// 更新type
  28. })
  29. }
  30. })
  31. }
  32. }

3.3 通过state传值(地址不变化)
  1. // params的默认值是{},而state的默认值是undefined。
  2. if(!this.props.location.state) return;
  3. 如果不是通过编程式导航跳过来,是取不到state的下面的值,反而还会报错。
  4. 刷新时不会丢失的
  5. 总结: 传多个值用state

3.4 Route的另外两个属性-可用于权限路由或者嵌套路由
  1. // render和children 若不传props则拿不到路由的三个属性
  2. <Route path="/test" render={(props)=>{return <Test {...props} a={666}/>}}/>
  3. <Route path="/test" children={(props)=>{return <Test {...props} a={666}/>}} />
  4. // children属性的特点: 无论是否匹配都会渲染(没匹配则match为空;匹配了match有值)。但是用的相对较少

4.对于路由三个属性的获取方法

  1. 只有Route组件的component属性属性渲染的组件才有路由的3个属性。
  2. Route的render和children必须通过回调往下传递props
  3. 自Route一步一步往下传props
  4. 不想传,或者拿不到就去找withRouter
    路由切换的组件才能拿到this.props中路由的3个属性。但是经过withRouter(一般最外层)返回的高阶组件可以是组件拥有路由的3个属性。

注意: 启动程序就用cmd

别用vs Code的终端启动项目(可能会出问题)、也不要用Git Bash Here启动项目后,端口不能释放,下次启动表示端口被占用

  1. 一用Redirect就报错
  2. NavLink的没有样式