第5章:React路由

417 阅读13分钟

需求:

1、点击左侧导航选项,展示对应的组件内容

2、点击Home组件的菜单,显示二级路由的组件

3、点击Message下的列表,显示三级路由的组件

4、三级路由的组件同属一个,根据路由传参显示不同内容

5、点击push查看,使用编程式实现push模式跳转

6、点击replace查看,使用编程式实现replace模式跳转

image.png

5.1 基本路由使用

5.1.1 路由的基本使用

概要总结

1、安装react-router-dom

2、创建主组件以及两个内容组件

3、创建路由链接

4、注册路由

5、全局使用同一个路由器

一、安装react-router-dom

npm i react-router-dom@5

注意:在2021年11月份升级到了6版本,可以在ReactRouter6教程详细讲解。

二、创建主组件以及两个内容组件

主组件App.js:

import React, {Component} from 'react';
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">
              <a className="list-group-item" href="./about.html">About</a>
              <a className="list-group-item active" href="./home.html">Home</a>
            </div>
          </div>
          <div className="col-xs-6">
            <div className="panel">
              <div className="panel-body">
                ??????
              </div>
            </div>
          </div>
        </div>
      </div>
    );
  }
}

内容组件About.jsx:

import React, {Component} from 'react';
export default class About extends Component {
  render() {
    return (
      <h3>我是About的内容</h3>
    );
  }
}

内容组件Home.jsx:

import React, {Component} from 'react';
export default class Home extends Component {
  render() {
    return (
      <h3>我是Home的内容</h3>
    );
  }
}

三、创建路由链接

1、引入标签

注意:to里的链接不要写成大写,例如/About应该写成/about。路径不要带上.,例如./about应该写成/about。

import {Link} from 'react-router-dom'
<div className="list-group">
  {/* 原生html中,靠<a>跳转不同的页面 */}
  {/*<a className="list-group-item" href="./about.html">About</a>*/}
  {/*<a className="list-group-item active" href="./home.html">Home</a>*/}
  {/* 在React中靠路由链接实现切换组件 */}
  <Link className="list-group-item" to="/about">About</Link>
  <Link className="list-group-item" to="/home">Home</Link>
</div>

2、引用标签

单单的使用<Link>标签替换了<a>标签实现路由链接,它会报错:你不应该在<Router>标签的外侧使用<Link>。

image.png

路由链接也好,路由也好,都得受到路由器的管理,它的意思是希望你在<Link>标签的外层包一层<Router>

import {Link, Router} from 'react-router-dom'

<div className="list-group">
  {/* 在React中靠路由链接实现切换组件 */}
  <Router>
   <Link className="list-group-item" to="/about">About</Link>
   <Link className="list-group-item" to="/home">Home</Link>
  </Router>
</div>

3、引用<BrowserRouter>标签

使用了<Router>标签包裹<Link>之后,它又会报另一个错误:它不能在undefined上读取location属性。而且它报的是Router内置库的错误。

image.png

这里报错不够精细,因为Router分为两种:BrowserRouter和HashRouter,一个是history一个是哈希。所以<Router>标签还是不行,因为它不知道你到底是哪一种路由器,可以选择<BrowserRouter>

import {Link, BrowserRouter} from 'react-router-dom'

<div className="list-group">
  {/* 在React中靠路由链接实现切换组件 */}
  <BrowserRouter>
    <Link className="list-group-item" to="/about">About</Link>
    <Link className="list-group-item" to="/home">Home</Link>
  </BrowserRouter>
</div>

image.png

image.png

四、注册路由

1、引入<Route>标签

import {Link, BrowserRouter, Route} from 'react-router-dom'

<div className="panel-body">
  {/* 注册路由 */}
  <Route path="/about" component={About} />
  <Route path="/home" component={Home} />
</div>

2、引用<BrowserRouter>标签

单单的使用<Route>标签实现路由注册,它会报错:你不应该在标签的外侧使用<Route>。

image.png

同理,使用<BrowserRouter>标签在外层包裹。

import {Link, BrowserRouter, Route} from 'react-router-dom'

<div className="panel-body">
  {/* 注册路由 */}
  <BrowserRouter>
    <Route path="/about" component={About} />
    <Route path="/home" component={Home} />
  </BrowserRouter>
</div>

五、全局使用同一个路由器

一个页面只能允许出现一个路由器,但是这里已经出现了两个<BrowserRouter>,一个在路由链接,一个在注册路由。所以在这种情况下是不能相互监测的。

image.png

最佳的办法就是在index.js里,直接在<App>根组件外面包一层<BrowserRouter>路由器,那无论里面有什么内容,绝对保证使用的是同一个路由器。

index.js:

import {BrowserRouter} from 'react-router-dom'; 
// 引入App组件
import App from './App'

const containter = document.getElementById('root')
const root = createRoot(containter)
// 渲染App到页面
root.render(
  <BrowserRouter>
    <App/>
  </BrowserRouter>
)

image.png

image.png

5.1.2 路由组件与一般组件

概要总结

1、路由组件

2、一般组件

3、路由组件与一般组件的区别

一、路由组件

1、路由组件指的是由路由注册的组件

2、路由组件放在pages目录下

二、一般组件

1、一般组件指的是由自己import引入的组件

2、一般组件放在components目录下

三、路由组件与一般组件的区别

这两种组件最大的区别在于props。

1、一般组件props

一般组件的props基于父组件的传值,父组件传什么props就是什么。

<Header a={1}/>
import React, {Component} from 'react';
export default class Header extends Component {
  render() {
    console.log('Header组件收到的props是', this.props)
    return (
      <div className="page-header">
        <h2>React Router Demo</h2>
      </div>
    );
  }
}

image.png

2、路由组件props

路由组件的props,就算没有传值,它的props也会默认接收到一些来自路由的参数。

<Link className="list-group-item" to="/home">Home</Link>
import React, {Component} from 'react';
export default class About extends Component {
  render() {
    console.log('About组件收到的props是', this.props)
    return ( <h3>我是About的内容</h3> );
  }
}

image.png

5.1.3 NavLink的使用

概要总结

1、NavLink的使用

2、使用activeClassName自定义类名

一、NavLink的使用场景

一般来说,在当前路由的链接需要加一个class类高亮显示。这时候可以使用来代替,因为它可以在当前路由下默认添加一个active类,也可以添加指定的class类名。

1、默认添加active类名

import {NavLink, Route} from 'react-router-dom' 
<div className="list-group">
  {/* 在React中靠路由链接实现切换组件--编写路由链接 */}
  <NavLink className="list-group-item" to="/about">About</NavLink>
  <NavLink className="list-group-item" to="/home">Home</NavLink>
</div>

image.png

2、使用activeClassName自定义类名

index.html:

<style>
  .atguigu {
    background-color: rgb(209, 137, 4) !important;
    color: white !important;
  }
</style>

App.js:

<div className="list-group">
  {/* 在React中靠路由链接实现切换组件--编写路由链接 */}
  <NavLink activeClassName="atguigu" className="list-group-item" to="/about">About</NavLink>
  <NavLink activeClassName="atguigu" className="list-group-item" to="/home">Home</NavLink> </div>

image.png

5.1.4 封装NavLink组件

概要总结

1、封装个NavLink组件

2、解决传参个数过多问题

3、使用children属性显示标签体内容

一、封装NavLink组件

对于NavLink我们可以进行二次封装,把固定的参数写在组件里,动态的通过传参方式传入组件内。

App.js:

<div className="list-group">
  {/* 在React中靠路由链接实现切换组件--编写路由链接 */}
  <MyNavLink to="/about"/>
  <MyNavLink to="/home"/>
</div>

MyNavLink.jsx:

import React, {Component} from 'react';
import {NavLink} from 'react-router-dom'

export default class MyNavLink extends Component {
  render() {
    const {to} = this.props
    return (
      <NavLink activeClassName="atguigu" className="list-group-item" to={to}>About</NavLink>
    );
  }
}

image.png

二、解决传参个数过多问题

如果参数过多,而且这些参数都是在<NavLink>标签上所需要用的,那么在组件接收的时候,需要一个一个参数通过props拿下来,然后再一个一个地写到<NavLink>里:

App.js:

<MyNavLink to="/about" title="About" a={1} b={2} c={3}/>

MyNavLink.jsx:

import React, {Component} from 'react';
import {NavLink} from 'react-router-dom'

export default class MyNavLink extends Component {
  render() {
    const {to, title, a, b, c} = this.props
    return (
      <NavLink activeClassName="atguigu" className="list-group-item" a={a} b={b} c={c} to={to}>{title}</NavLink>
    );
  }
}

在React中,可以通过展开运算符,把对象展开然后传给组件即可,例如<NavLink {...this.props}/>

import React, {Component} from 'react';
import {NavLink} from 'react-router-dom'

export default class MyNavLink extends Component {
  render() {
    const {title} = this.props
    return (
      <NavLink activeClassName="atguigu" className="list-group-item" {...this.props}>{title}</NavLink>
    );
  }
}

三、标签体内容传参

1、使用children接参

对于标签属性可以通过props传递,其实标签体内容也是一个特殊的标签属性。

<MyNavLink to="/about" a={1} b={2} c={3}>About</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink>

image.png

react帮我们把标签体内容用children作为key传递过去了。

2、优化子组件标签体

对于子组件而言,父组件通过children传递了标签体内容,那么子组件的标签体就应该使用this.props.children来显示:

import React, {Component} from 'react';
import {NavLink} from 'react-router-dom'

export default class MyNavLink extends Component {
  render() {
    console.log(this.props)
    return (
      <NavLink activeClassName="atguigu" className="list-group-item" {...this.props}>{this.props.children}</NavLink>
    );
  }
}

这里有一个简写的形式,就是标签体可以不写,只要组件存在children属性,标签体内容同样能够显示:

<NavLink activeClassName="atguigu" className="list-group-item" {...this.props} />

image.png

5.1.5 Switch的使用

概要总结

1、注册重复路由

2、Switch标签的使用

一、注册重复路由

如果在注册路由的时候,注册了两个相同的路径,但匹配不同的组件。这个时候react会把两个组件全都渲染上去,例如:

App.js:

<div className="panel-body">
  {/* 注册路由 */}
  <Route path="/about" component={About} />
  <Route path="/home" component={Home} />
  <Route path="/home" component={Test} />
</div>

image.png

这种情况的出现显然是不合理的,正常情况下肯定是一个路径对应一个组件,如果需要匹配多个组件,那就应该把各个组件组装在一起。

二、Switch标签

按照react路由匹配的原理,它是由上往下匹配的,而且就算匹配到了还会继续往下匹配,所以它才会出现同一个路由匹配了多个组件的情况。

这样会产生一个问题,如果注册的路由非常多,而每个路由它又是全部遍历,性能就会特别差,而且也不符合匹配原则。Switch标签的作用是只要路由一旦匹配成功,它就跳出去不再往下匹配:

App.js:

import {Route, Switch} from 'react-router-dom'

<div className="panel-body">
  {/* 注册路由 */}
  <Switch>
    <Route path="/about" component={About} />
    <Route path="/home" component={Home} />
    <Route path="/home" component={Test} />
  </Switch>
</div>

image.png

5.1.6 解决样式丢失问题

概要总结

1、多层级路由产生样式加载路径错误

2、三种样式路径错误解决方案

(1)绝对路径

(2)%PUBLIC_URL%

(3)使用哈希路由HashRouter

一、样式加载路径出错

如果在注册路由的时候,路径是多层级的,例如/atguigu/about,这样刷新页面的时候,样式加载的路径会发生以下的错误:

App.js:

<div className="panel-body">
  {/* 注册路由 */}
  <Switch>
    <Route path="/atguigu/about" component={About} />
    <Route path="/atguigu/home" component={Home} />
  </Switch>
</div>

image.png

react里只要你请求的资源不存在,它就会把public里的index.html返回给你:

image.png

二、解决样式路径错误方案

1、绝对路径

在index.html引入样式的时候,使用的是绝对路径/,不要用相对路径./。

<link rel="stylesheet" href="/css/bootstrap.css">

2、%PUBLIC_URL%

%PUBLIC_URL%是react内定的一个指向public目录的标识符。

<link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap.css">

3、使用哈希路由HashRouter

HashRouter与BrowserRouter的区别就在于url路径是否有#,#号后面的都认为是前端的资源,不会带给服务器。所以在读取资源的时候,它会自动忽略#后面的路径,只会拿到前面的ip端口。

// 引入ReactDOM的createRoot
import {createRoot} from 'react-dom/client'
// 引入react-router-dom库
import {HashRouter} from 'react-router-dom';
// 引入App组件 import App from './App'

const containter = document.getElementById('root')
const root = createRoot(containter)
// 渲染App到页面
root.render(
  <HashRouter>
    <App/>
  </HashRouter>
)

5.1.7 路由的模糊匹配与严格匹配

概要总结

1、模糊匹配

2、严格匹配

一、模糊匹配

当路由链接的to与注册路由的path不一致的时候,要分两种情况。

1、to的链接包含path

如果to的链接比注册路由的path还要长,路由是可以匹配成功的。

<div className="list-group">
  <MyNavLink to="/about">About</MyNavLink>
  <MyNavLink to="/home/a/b">Home</MyNavLink>
</div>
<div className="panel-body">
  {/* 注册路由 */}
  <Switch>
    <Route path="/about" component={About} />
    <Route path="/home" component={Home} />
  </Switch>
</div>

image.png

2、path的路径包含to

如果注册路由的path比to要长,这样是匹配不成功的,react要求to的路由链接最起码要满足path的长度。

<div className="list-group">
  <MyNavLink to="/about">About</MyNavLink>
  <MyNavLink to="/home">Home</MyNavLink>
</div>
<div className="panel-body">
  {/* 注册路由 */}
  <Switch>
    <Route path="/about" component={About} />
    <Route path="/home/a/b" component={Home} />
  </Switch>
</div>

image.png

总结:路由需要匹配的path,可以给多但不能给少,而且是左往右匹配,如果不是按顺序,即使包含也不能匹配,例如/a/home/b无法匹配/home:

<div className="list-group">
  <MyNavLink to="/about">About</MyNavLink>
  <MyNavLink to="/a/home/b">Home</MyNavLink>
</div>
<div className="panel-body">
  {/* 注册路由 */}
  <Switch>
    <Route path="/about" component={About} />
    <Route path="/home" component={Home} />
  </Switch>
</div>

image.png

二、严格匹配

严格匹配就是to和path不多不少,完全一致。可以通过在里添加exact设置为true即可。

注意:严格匹配模式不要随便开启,开启了有时候会导致无法继续匹配二级路由,除非是路由跳转出问题可以考虑使用。

<div className="list-group">
  <MyNavLink to="/about">About</MyNavLink>
  <MyNavLink to="/home/a/b">Home</MyNavLink>
</div>
<div className="panel-body">
  {/* 注册路由 */}
  <Switch>
    <Route exact path="/about" component={About} />
    <Route exact path="/home" component={Home} />
  </Switch>
</div>

image.png

image.png

5.1.8 Redirect的使用

概要总结

1、Redirect的使用

一、Redirect的使用

当路由在所有注册路由都没有匹配上的时候,可以使用Redirect标签跳转到指定的路由。它通常用在一个默认的初始化路由。

image.png

通常我们会指定每一个页面对应的路由,但一般不会指定根目录的路由,此时可以通过Redirect标签去重定向到其它的路由:

import {Route, Switch, Redirect} from 'react-router-dom'
<div className="panel-body">
  {/* 注册路由 */}
  <Switch>
    <Route path="/about" component={About} />
    <Route path="/home" component={Home} />
    <Redirect to="/about"/>
  </Switch>
</div>

注意:Redirect标签一般配置在所有路由的最末尾的地方。

5.2 嵌套路由使用

5.2.1 嵌套路由

概要总结

1、创建两个二级路由组件

2、一级路由组件注册二级路由

3、路由匹配原则

一、创建两个二级路由组件

Home/News/index.jsx:

import React, {Component} from 'react';

export default class News extends Component {
  render() {
    return (
      <ul>
        <li>news001</li>
        <li>news002</li>
        <li>news003</li>
      </ul>
    );
  }
}

Home/Message/index.jsx:

import React, {Component} from 'react';
export default class Message extends Component {
  render() {
    return (
      <ul>
        <li><a href="/message1">message001</a></li>
        <li> <a href="/message2">message002</a></li>
        <li> <a href="/message/3">message003</a></li>
      </ul>
    );
  }
}

二、一级路由组件注册二级路由

Home/index.jsx:

import React, {Component} from 'react';
import {Route, Switch, Redirect} from 'react-router-dom'
import MyNavLink from '../../components/MyNavLink'
import News from './News'
import Message from './Message'

export default class Home extends Component {
  render() {
    return (
      <div>
        <div>
          <h3>我是Home的内容</h3>
          <ul className="nav nav-tabs">
            <li>
              <MyNavLink to="/home/news">News</MyNavLink>
            </li>
            <li>
              <MyNavLink to="/home/message">Message</MyNavLink>
            </li>
          </ul>
          {/* 注册路由 */}
          <Switch>
            <Route path="/home/news" component={News}/>
            <Route path="/home/message" component={Message}/>
            <Redirect to="/home/news"/>
          </Switch>
        </div>
      </div>
    );
  }
}

image.png

image.png

三、路由匹配原则

1、路由注册有先后顺序

一级路由app.js:

<Switch>
  <Route path="/about" component={About} />
  <Route path="/home" component={Home} />
  <Redirect to="/about"/>
</Switch>

二级路由Home/index.jsx:

<Switch>
  <Route path="/home/news" component={News}/>
  <Route path="/home/message" component={Message}/>
  <Redirect to="/home/news"/>
</Switch>

About与Home组件是属于一级路由组件,News与Message组件属于Home组件的二级路由组件。在一级路由中,路由路径/home对应的是Home组件,在二级路由自身的路径是/news和/message,但是前面要把一级路由路径/home也带上。

这是因为在二级路由中,访问的路径假设是/home/message,那么react会从一级路由到二级路由逐个逐个去匹配。首先匹配到一级路由的/home,这个是匹配成功的,所以Home组件渲染出来。然后由于Home组件里也注册了路由,因此继续匹配到二级路由的/home/message,Message组件也渲染出来。

如果二级路由的message路径并没有带上一级路径/home,而是直接写/message,那么根据路由匹配的先后顺序原则,在第一层路由就只有/about和/home,否则就去Redirect重定向到/about:

<div>
  <h3>我是Home的内容</h3>
  <ul className="nav nav-tabs">
    <li>
      <MyNavLink to="/news">News</MyNavLink>
    </li>
    <li>
      <MyNavLink to="/message">Message</MyNavLink>
    </li>
  </ul>
</div>

image.png

image.png

2、路由的严格模式

一旦开启了路由的严格模式,那就意味着路由路径必须精准匹配,但如果在一级路由就开启严格模式,那么会直接导致二级路由无法匹配。

因为二级路由假设/home/message,它来到一级路由匹配的时候,发现/home是开启了严格模式,因此匹配失败了,直接Redirect到/about。一级路由匹配失败,那二级路由就更不用匹配了。

<Switch>
  <Route path="/about" component={About} />
  <Route exact path="/home" component={Home} />
  <Redirect to="/about"/>
</Switch>

image.png

image.png

5.3 向路由组件传递参数数据

5.3.1 向路由组件传递params参数

概要总结

1、创建一个三极路由组件

2、二级路由组件注册三级路由

3、路由组件传递params参数

一、创建一个三级路由组件

Home/Message/Detail/index.jsx:

import React, {Component} from 'react'
export default class Index extends Component {
  render() {
    return (
      <ul>
        <li>ID:???</li>
        <li>TITLE:???</li>
        <li>CONTENT:???</li>
      </ul>
    );
  }
}

二、二级路由组件注册三级路由

Home/message/index.jsx:

import React, {Component} from 'react';
import {Link, Route} from 'react-router-dom';
import Detail from './Detail'

export default class Message extends Component {
  state = {
    messageArr: [
      {id: '01', title: '消息1'},
      {id: '02', title: '消息2'},
      {id: '03', title: '消息3'}
    ]
  }
  render() {
    const {messageArr} = this.state
    return (
      <div>
        <ul>
          {
            messageArr.map(msgObj => {
              return (
                <li key={msgObj.id}>
                  <Link to="/home/message/detail">{msgObj.title}</Link>
                </li>
              )
            })
          }
        </ul>
        <hr />
        <Route path="/home/message/detail" component={Detail} />
      </div>
    );
  }
}

image.png

三、路由组件传递params参数

1、路由链接传递params参数

传递params参数,其实就是把参数拼在路径的后面,类似于ajax的params参数。

Home/message/index.jsx:

<ul>
  {
    messageArr.map(msgObj => {
      return (
        <li key={msgObj.id}>
          <Link to={`/home/message/detail/${msgObj.id}`}>{msgObj.title}</Link>
        </li>
      )
    })
  }
</ul>
<hr />
<Route path="/home/message/detail" component={Detail} />

image.png

2、注册路由声明变量接收参数

这样子虽然已经把参数拼在了路径的后面,但这对于路由来说只是模糊匹配,组件内部是无法接受到的,因此在注册路由中还需要声明接收params参数:

{/* 声明接收params参数 */}
<Route path="/home/message/detail/:id" component={Detail} />

如果需要传递多个参数,那就声明多个变量来接收:

<ul>
  {
    messageArr.map(msgObj => {
      return (
        <li key={msgObj.id}>
          {/* 向路由组件传递params参数 */}
          <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
        </li>
      )
    })
  }
</ul>
<hr />
{/* 声明接收params参数 */}
<Route path="/home/message/detail/:id/:title" component={Detail} />

声明了变量接收参数之后,在路由组件内部就可以通过props查看参数了,它就存放在match里的params中:

image.png

5.3.2 向路由组件传递search参数

概要总结

1、路由组件传递search参数

2、querystring库的使用

3、使用querystring库解析search参数

一、路由组件传递search参数

1、路由链接传递search参数

传递search参数,其实就是把参数通过?和&以键值对的方式拼在路径的后面,类似于ajax的query参数。

Home/message/index.jsx:

<ul>
  {
    messageArr.map(msgObj => {
      return (
        <li key={msgObj.id}>
          {/* 向路由组件传递search参数 */}
          <Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>
        </li>
      )
    })
  }
</ul>

2、search参数无需声明接收

接收search参数与接收params参数不同,接收params参数在注册路由的时候是需要声明变量对应接收的,而search参数由于已经把字段和值全部写在了路径上,因此它不用再声明了,跟普通注册路由一样即可。

{/* search参数无需声明接收 */}
<Route path="/home/message/detail" component={Detail} />

声明了变量接收参数之后,在路由组件内部就可以通过props查看参数了,它就存放在location里的search中:

image.png

二、querystring库的使用

1、安装querystring依赖

npm i querystring

2、将对象转换成urlencoded编码

多种key=value用&分隔的编码形式叫做urlencoded编码。通过querystring的stringify方法把对象转换成urlencoded编码。

let obj = {name: 'tom', age: 18}
console.log(qs.stringify(obj)) // name=tom&age=18 key=value&key=value

3、将urlencoded编码转换成对象

通过querystring的parse方法把urlencoded编码转换成对象。

let str = 'carName=奔驰&price=199'
console.log(qs.parse(str)) // {carName: '奔驰', price: '199'}

三、使用querystring库解析search参数

我们拿到search参数之后,截取掉第一个?字符,实际上它是一个urlencoded编码格式,还需要进一步转换成对象。我们可以通过querystring库的parse方法进行解析。

render() {
  // 接收search参数
  const {search} = this.props.location // ?id=01&title=消息1
  const {id, title} = qs.parse(search.slice(1)) // {id: '01', title: '消息1'}
  const findResult = DetailData.find(detailObj => {
    return detailObj.id === id
  })
  return (
    <ul>
      <li>ID:{id}</li>
      <li>TITLE:{title}</li>
      <li>CONTENT:{findResult.content}</li>
    </ul>
  );
}

image.png

5.3.3 向路由组件传递state参数

概要总结

1、路由组件传递state参数

2、state参数具备缓存作用

一、路由组件传递state参数

1、路由链接传递state参数

无论是params参数还是search参数,都会暴露在url栏地址上。而state参数是隐藏的,不会暴露在url上。

传递state参数,或者的to不能直接传地址,而是要传一个对象,对象里的pathname代表路由路径,而state是传参的键值对。

Home/message/index.jsx:

<ul>
  {
    messageArr.map(msgObj => {
      return (
        <li key={msgObj.id}>
          {/* 向路由组件传递state参数 */}
          <Link to={{pathname: '/home/message/detail', state: {id: msgObj.id, title: msgObj.title}}}>{msgObj.title}</Link>
        </li>
      )
    })
  }
</ul>

2、state参数无需声明接收

接收state参数与接收search参数一样,不需要声明变量来接收,跟普通注册路由一样即可。

{/* state参数无需声明接收 */}
<Route path="/home/message/detail" component={Detail} />

声明了变量接收参数之后,在路由组件内部就可以通过props查看参数了,它就存放在location里的state中:

image.png

二、state参数具备缓存作用

相比params和search两种参数形式,state是惟一一种不在url路径暴露参数的形式,但是它在刷新的时候,它依然知道上一次的记录。

由于我们用的是BrowserRouter,它一直在操作浏览器的history,所以state参数虽然没有暴露参数,但是history是有记录的。在props里的location的state参数,实际上它同样存在于props的history/location/state,location是history的子属性而已。

image.png

如果此时把浏览器缓存清空,这样所有的历史记录都删掉,那么state当然也就不存在了:

image.png

image.png

因此在使用state参数的时候,要注意处理第一次history没有记录的时候的兼容。

5.3.4 总结路由参数

概要总结

1、路由参数总结

一、向路由组件传递参数

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/:name/:age" component={Test}/>

接收参数:this.props.location.search

备注:获取到的search是urlencoded编码字符串,需要借助querystring解析

3、state参数

路由链接(携带参数):<Linkto={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>

注册路由(无需声明,正常注册即可):<Route path="/demo/test/:name/:age" component={Test}/>

接收参数:this.props.location.state

备注:刷新也可以保留住参数

5.4 多种路由跳转方式

5.4.1 push与replace

概要总结

1、push与replace

一、push与replace

路由的工作原理,其实就是对浏览器的历史记录进行操作,一共是两种:push和replace。push是一个压栈的操作,会留下痕迹。而replace会替换掉当前的记录,不会留下痕迹。

路由它默认的是push操作,如下图所示:

image.png

如果需要开启replace模式,其实只需要在加一个replace即可:

<ul>
  {
    messageArr.map(msgObj => {
      return (
        <li key={msgObj.id}>
          {/* 向路由组件传递state参数 */}
          <Link replace to={{pathname: '/home/message/detail', state: {id: msgObj.id, title: msgObj.title}}}>{msgObj.title}</Link>
        </li>
      )
    })
  }
</ul>

注意:开启了replace模式,也就相当于开启了无痕模式。

5.4.2 编程式路由导航

概要总结

1、编程式路由导航

2、路由的前进与后退

一、编程式路由导航

编程式路由导航,简单来说就是不借助或者,而是通过代码来实现路由跳转。

react路由组件里,props的history提供了push和replace两个方法分别实现编程式的push模式和replace模式,即this.props.history.push()和this.props.history.replace()。

image.png

以下是push和replace模式的params、search、state三种模式的路由方法:

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})
}
pushShow = (id, title) => {
  // push跳转+携带params参数
  this.props.history.push(`/home/message/detail/${id}/${title}`)
  
  // push跳转+携带search参数
  // this.props.history.push(`/home/message/detail?id=${id}&title=${title}`)
  
  // push跳转+携带state参数
  // this.props.history.push('/home/message/detail', {id, title}) }

在注册路由的时候,注意分别对应这3种模式来注册即可:

{/* 声明接收params参数 */}
<Route path="/home/message/detail/:id/:title" component={Detail} />
{/* search参数无需声明接收 */}
{/*<Route path="/home/message/detail" component={Detail} />*/}
{/* state参数无需声明接收 */}
{/*<Route path="/home/message/detail" component={Detail} />*/}

二、路由的前进与后退

react路由组件里,props的history还提供了go、goBack和goForward三个方法,用于控制路由的前进和后退。goBack方法回退一步,goForward前进一步,go方法通过传参控制,1代表前进一步,-1代表后退一步,以此类推。

back = () => {
  this.props.history.goBack()
}
forward = () => {
  this.props.history.goForward()
}
go = () => {
  this.props.history.go(-2)
}

image.png

5.4.3 withRouter的使用

概要总结

1、withRouter的使用

一、withRouter的使用

通过路由注册的组件称之为路由组件,这些组件react会通过props传递一系列的路由参数,例如history、location、match等等。

对于一般组件来说,props是不会有这些路由参数的。

image.png

如果一般组件也想使用路由的方法,可以借助于withRouter。withRouter可以接收一个组件,它的作用是给一般组件加上路由的属性。

import React, {Component} from 'react';
import {withRouter} from 'react-router-dom'

class Header extends Component {
  render() {
    console.log('Header组件收到的props是', this.props)
    return ( ...... );
  }
}
export default withRouter(Header)

image.png

5.4.4 BrowserRouter与HashRouter

概要总结

1、BrowserRouter对比HashRouter

一、底层原理不一样

BrowserRouter使用的是H5的history API,不兼容IE9及以下版本

HashRouter使用的是URL的哈希值。

二、url表现形式不一样

BrowserRouter的路径中没有#,例如:localhost:3000/demo/test

HashRouter的路径中包含#,例如:localhost:3000/#/demo/test

三、刷新后对路由state参数的影响

BrowserRouter没有任何影响,因为state保存在history对象中

HashRouter刷新后会导致路由state参数的丢失

备注:HashRouter可以用于解决一些路径错误相关的问题。

5.5 代码地址

gitee.com/huang_jing_…