React路由

文章目录

  • 单页面应用spa
  • 路由
    • 路由是什么
    • React路由原理
      • 点击页面选项路径改变
      • 路径改变页面变化
    • React路由的使用
      • 实现点击页面选项路径改变——编写路由链接
      • 根据路径显示组件 ——注册路由
      • 组件的分类——普通组件和路由组件
      • 案例
      • 实现选中高亮效果——NavLink
      • NavLink的封装
      • Switch的使用
      • 补充:样式丢失问题
        • 解决·:
      • 路由的严格匹配与模糊匹配
        • 模糊匹配
        • 严格匹配
    • Redirect重定向的使用
    • 嵌套路由
    • 路由传参
      • 向路由组件传递params参数
        • 使用方法
        • 例子
      • 向路由组件传递search参数(query参数)
        • 补充:qs库
        • 使用方式
        • 例子
      • 向路由组件传递state参数
        • 使用方式
        • 例子
    • 路由的两种模式push与replace
    • 编程式路由导航
    • withRouter的使用(一般组件实现编程式路由导航)
    • BrowserRouter 和 HashRouter的区别

单页面应用spa

多页面应用是指每切换一个页面就是一个真正的html页面。
单页面应用是指整个应用只有一个完整的页面,点击页面中的链接不会刷新页面只会局部更新,并且数据都需要通过ajax请求获取,并在前端异步展示。

单页面,多组件。

路由

路由是什么

  1. 什么是路由? —— Route
    —个路由就是一个映射关系 ( key:value )
    key为路径, value 可能是function(后端路由)或component(前端路由)路由分类
  • 后端路由:
    理解:value是function,用来处理客户端提交的请求。
    注册路由: router.get(path, function(req, res))
    工作过程:当node接收到一个请求时,根据请求路径找到匹配的路由,调用路
    由中的函数来处理请求返回响应数据
  • 前端路由:
    浏览器端路由,value是component,用于展示页面内容。
    注册路由:
    工作过程:当浏览器的path变为/test时,当前路由组件就会变为Test组件
  1. 路由器 —— Router
    路由器是用于管理路由的。

React路由原理

React路由_第1张图片
路由匹配的是只是端口号后面的内容(/about、/history)。

点击页面选项路径改变

那么是如何实现一点击导航栏的选项浏览器地址栏中的路径就改变呢, 这就需要借助history实现。
浏览器的BOM身上有一个history,用于管理浏览器的路径、历史记录等。
但是BOM原生的history不好操作我们一般用一个封装好的库history.js,(该库实际上操作的也是BOM身上的history)引入:

<script type="text/javascript" src="https://cdn.bootcss.com/history/4.7.2/history.js">script>

,使用:

  1. 引入:
let history = History.createBrowserHistory()
  1. 推入
// 将路径放到历史记录history中
	history.push(path)
  1. 替换
// 将路径放到历史记录history中
	history.replace(path)
  1. 监听路径变化
// history的listen方法可以监听路径的修改
	history.listen((location) => {
		console.log('请求路由路径变化了', location)
	})
  1. 前进
	history.goForward()
  1. 后退
	history.goBack()

history的数据结构是栈结构:
React路由_第2张图片
栈顶的路径就是当前页面显示的内容。

History.createBrowserHistory()是使用的是h5身上的history方法,在某些旧的浏览器不支持。
可以使用: History.createHashHistory(),和createBrowserHistory的区别是路径中会多出一个 # ,虽然不好看但是兼容性特别好。

History.createBrowserHistory():

在这里插入图片描述

History.createHashHistory():

在这里插入图片描述

路径改变页面变化

React会监听路径,当路径发生变化时,被前端路由器检测到,就会进行路由匹配重新渲染页面。

React路由的使用

React路由的实现需要借助react的路由插件库:react-router-dom, 专门用于实现SPA应用。

  • 安装:
npm i react-router-dom

实现点击页面选项路径改变——编写路由链接

直接利用从 react-router-dom 库中引入的路由器Router标签就可以实现:

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

<BrowserRouter>
    <Link className="list-group-item" to="/about">About</Link>
    <Link className="list-group-item" to="/home">Home</Link>
 </BrowserRouter>

点击About浏览器的路径就变成 /about, 点击Home浏览器的路径就变成/home

有两种Router标签:,在Router标签中再使用进行路由。
标签就对应history的browser模式
标签就对应history的hash模式

(最后在浏览器渲染的时候,Link标签实际上是会转换为a标签的)

根据路径显示组件 ——注册路由

直接利用从 react-router-dom 库中引入的路由Route 进行路由的注册:

   import { Route } from 'react-router-dom'
{/* 注册路由 */}
  	<Route path="/about" component={About}/>
    <Route path="/home" component={Home}/>

也需要使用Router标签进行包裹,但是编写路由链接的路由器应该和注册路由的路由器是一个路由器才对,可以直接将组件使用Router标签包裹即可。

index.js

// 引入react核心库
import React from 'react'
// 引入ReactDOM
import ReactDOM from 'react-dom'
// 引入路由组件
import { BrowserRouter } from 'react-router-dom'
// 引入App组件
import App from './App'

// 渲染App到页面
ReactDOM.render(
   <BrowserRouter>
       <App/>
   </BrowserRouter>, 
   document.getElementById('root'))

组件的分类——普通组件和路由组件

像上面的例子通过路由来使用组件:,这样的组件叫做路由组件。还有一类就是通过标签正常使用的组件如,这样的组件就是普通组件。
普通组件一般直接放在src/component文件夹中,路由组件一般放在src/pages中。

普通组件和路由组件一个最大的区别就是,路由组件可以接收到路由器传递的参数。如当点击上述例子的About按钮时,接收到的参数为:
在这里插入图片描述

  • history :
    go:可以穿参数n,表示前进或后退|n|步,n>0表示前进n步,n<0表示后退n步。
    goBack:后退1步
    goForward:前进1步
    push:push路由
    replace:replace路由
    location:(和下面的这个location是同一个对象)
  • location
    pathname:获取路由路径
    search:(存储swarch参数)
    state:(存储state参数)
  • match
    params:(存储params参数)
    path:获取路由路径
    url:获取路由路径

案例

实现效果:
React路由_第3张图片
React路由_第4张图片
代码:

React路由_第5张图片
src/index.js

// 引入react核心库
import React from 'react'
// 引入ReactDOM
import ReactDOM from 'react-dom'
// 引入路由组件
import { BrowserRouter } from 'react-router-dom'
// 引入App组件
import App from './App'

// 渲染App到页面
ReactDOM.render(
    <BrowserRouter>
        <App/>
    </BrowserRouter>, document.getElementById('root'))


src/App.jsx

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

import About from './pages/About'
import Home from './pages/Home'
import Header from "./components/Header"
export default class App extends Component {
  render() {
    return (
      <div>
        <Header/>
        <div className="row">
          <div className="col-xs-2 col-xs-offset-2">
            <div className="list-group">
              {/* 原生直接使用a标签进行切换 */}
              {/* About
              Home */}

              {/* react中靠路由链接进行切换 */}
              {/* 
                有两种Router:
                  
                  
                  再Router标签中再使用Link进行路由
               */}
                  <Link className="list-group-item" to="/about">About</Link>
                  <Link className="list-group-item" to="/home">Home</Link>
            </div>
          </div>
          <div className="col-xs-6">
            <div className="panel">
              <div className="panel-body">
                {/* 注册路由 */}
                <Route path="/about" component={About}/>
                <Route path="/home" component={Home}/>
              </div>
            </div>
          </div>
        </div>
      </div>
    )
  }
}

page/About/index.jsx

import React, { Component } from 'react'

export default class About extends Component {
  render() {
    console.log("接收到参数:", this.props);
    return (
      <div>About ... </div>
    )
  }
}

page/Home/index.jsx

import React, { Component } from 'react'

export default class Home extends Component {
  render() {
    return (
      <div>Home ... </div>
    )
  }
}

components/Header/index.jsx

import React, { Component } from 'react'

export default class index extends Component {
  render() {
    return (
        <div className="row">
            <div className="col-xs-offset-2 col-xs-8">
                <div className="page-header"><h2>React Router Demo</h2></div>
            </div>
        </div>
    )
  }
}

实现选中高亮效果——NavLink

需要借助 实现, 标签配合 activeClassName='类名' 实现,当点击该标签 该样式就会显示。

activeClassName='类名'中的类名默认是active。
eg:
上述案例修改App.jsx

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

import About from './pages/About'
import Home from './pages/Home'
import Header from "./components/Header"
export default class App extends Component {
  render() {
    return (
      <div>
        <Header/>
        <div className="row">
          <div className="col-xs-2 col-xs-offset-2">
            <div className="list-group">
                  <NavLink activeClassName='active' className="list-group-item" to="/about">About</NavLink>
                  <NavLink activeClassName='active' className="list-group-item" to="/home">Home</NavLink>
            </div>
          </div>
          <div className="col-xs-6">
            <div className="panel">
              <div className="panel-body">
                {/* 注册路由 */}
                <Route path="/about" component={About}/>
                <Route path="/home" component={Home}/>
              </div>
            </div>
          </div>
        </div>
      </div>
    )
  }
}

效果:
React路由_第6张图片
注意:这里我在public文件夹中已经引入了bootstrap.css样式,所以会有样式效果

NavLink的封装

知识点:

  • 组件标签的标签体中的内容会作为children属性传递给组件
  • 在组件中通过children属性就可以指定标签体的内容。

即:
App.jsx

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

import About from './pages/About'
import Home from './pages/Home'
import Header from "./components/Header"
import MyNavLink from './components/MyNavLink'
export default class App extends Component {
  render() {
    return (
      <div>
        <Header/>
        <div className="row">
          <div className="col-xs-2 col-xs-offset-2">
            <div className="list-group">
                    {/* 标签体中的内容会作为children属性传递给组件 */}
                  <MyNavLink to="/home">Home</MyNavLink>
                  <MyNavLink to="/about">About</MyNavLink>
            </div>
          </div>
          <div className="col-xs-6">
            <div className="panel">
              <div className="panel-body">
                {/* 注册路由 */}
                <Route path="/about" component={About}/>
                <Route path="/home" component={Home}/>
              </div>
            </div>
          </div>
        </div>
      </div>
    )
  }
}

MyNavLink,jsx

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

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

Switch的使用

一般情况下一个路径(path)只对应一个组件,如果一个路径对应多个组件会是什么效果呢,答案是多个组件都会显示,如:

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

显示如下:
React路由_第7张图片
说明当路径匹配完成之后如果再出现同样的路径就会再次匹配,但是一般情况下一个路径对应一个组件就可以了,所以说为了提升效率,我们希望当匹配到一个路径之后,再碰到该路径就不会进行匹配了。这就可以借助组件,用组件包裹路由。

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

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

补充:样式丢失问题

启动React脚手架,浏览器访问http://localhost:3000/favicon.ico,代表访问public文件夹下的favicon.ico资源。即本地服务器的根路径就是/public文件夹,如果/public文件夹中没有对应的资源就会返回/public/index.html内容。

当路由路径是多级路径,并且刷新页面的时候就会出现样式丢失。因为他会把一级路由放在请求路径进行请求。
eg:
app.jsx

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

import About from './pages/About'
import Home from './pages/Home'
import Header from "./components/Header"
import MyNavLink from './components/MyNavLink'
export default class App extends Component {
  render() {
    return (
      <div>
        <Header/>
        <div className="row">
          <div className="col-xs-2 col-xs-offset-2">
            <div className="list-group">
                    {/* 标签体中的内容会作为children属性传递给组件 */}
                  <MyNavLink to="/yang/home">Home</MyNavLink>
                  <MyNavLink to="/yang/about">About</MyNavLink>
            </div>
          </div>
          <div className="col-xs-6">
            <div className="panel">
              <div className="panel-body">
                {/* 注册路由 */}
                <Switch>
                  <Route path="/yang/about" component={About}/>
                  <Route path="/yang/home" component={Home}/>
                </Switch>
              </div>
            </div>
          </div>
        </div>
      </div>
    )
  }
}

浏览器:
React路由_第8张图片

解决·:
  • 法一: index.html中引入样式的时候使用相对路径:
<link rel="stylesheet" href="/css/bootstrap.css">
  • 法二: 使用 %PUBLIC_URL%%PUBLIC_URL% 代表的是public文件夹的绝对路径
<link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap.css">
  • 法三:使用HashRouter而不是BrowserRouter。
    index.jsx
// 引入react核心库
import React from 'react'
// 引入ReactDOM
import ReactDOM from 'react-dom'
// 引入路由组件
import { HashRouter } from 'react-router-dom'
// 引入App组件
import App from './App'

// 渲染App到页面
ReactDOM.render(
    <HashRouter>
        <App/>
    </HashRouter>, document.getElementById('root'))

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

模糊匹配

一般情况下:NavLinkto 属性和Routepath属性匹配到的路径是一样的。
NavLinkto 属性是多级路由,但是Routepath只是匹配到第一级路由,那么组件会进行展示;
但是如果当 NavLinkto 属性是一级路由,但是Routepath匹配的是多级路由,组件是不会进行展示的。
这就是模糊匹配。

eg:

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

import About from './pages/About'
import Home from './pages/Home'
import Header from "./components/Header"
import MyNavLink from './components/MyNavLink'
export default class App extends Component {
  render() {
    return (
      <div>
        <Header/>
        <div className="row">
          <div className="col-xs-2 col-xs-offset-2">
            <div className="list-group">
                    {/* 标签体中的内容会作为children属性传递给组件 */}
                  <MyNavLink to="/home/a/b">Home</MyNavLink>
                  <MyNavLink to="/about/c/d">About</MyNavLink>
            </div>
          </div>
          <div className="col-xs-6">
            <div className="panel">
              <div className="panel-body">
                {/* 注册路由 */}
                <Switch>
                  <Route path="/home" component={Home}/>
                  <Route path="/about" component={About}/>
                </Switch>
              </div>
            </div>
          </div>
        </div>
      </div>
    )
  }
}

组件正常显示:
React路由_第9张图片
但是如果是:

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

import About from './pages/About'
import Home from './pages/Home'
import Header from "./components/Header"
import MyNavLink from './components/MyNavLink'
export default class App extends Component {
  render() {
    return (
      <div>
        <Header/>
        <div className="row">
          <div className="col-xs-2 col-xs-offset-2">
            <div className="list-group">
                    {/* 标签体中的内容会作为children属性传递给组件 */}
                  <MyNavLink to="/home">Home</MyNavLink>
                  <MyNavLink to="/about">About</MyNavLink>
            </div>
          </div>
          <div className="col-xs-6">
            <div className="panel">
              <div className="panel-body">
                {/* 注册路由 */}
                <Switch>
                  <Route path="/home/a/b" component={Home}/>
                  <Route path="/about/c/d" component={About}/>
                </Switch>
              </div>
            </div>
          </div>
        </div>
      </div>
    )
  }
}

React路由_第10张图片
组件就不展示了。

严格匹配

就是NavLinkto 属性和Routepath属性匹配到的路径必须是一样的。
开启严格匹配的方式:给 标签添加 exact 属性。

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

import About from './pages/About'
import Home from './pages/Home'
import Header from "./components/Header"
import MyNavLink from './components/MyNavLink'
export default class App extends Component {
  render() {
    return (
      <div>
        <Header/>
        <div className="row">
          <div className="col-xs-2 col-xs-offset-2">
            <div className="list-group">
                    {/* 标签体中的内容会作为children属性传递给组件 */}
                  <MyNavLink to="/home">Home</MyNavLink>
                  <MyNavLink to="/about">About</MyNavLink>
            </div>
          </div>
          <div className="col-xs-6">
            <div className="panel">
              <div className="panel-body">
                {/* 注册路由 */}
                <Switch>
                  <Route exact={true} path="/home/a/b" component={Home}/>
                  <Route exact={true} path="/about/c/d" component={About}/>
                </Switch>
              </div>
            </div>
          </div>
        </div>
      </div>
    )
  }
}

严格匹配不要随便开启,可能会导致二级路由无法使用。

Redirect重定向的使用

当Route指明的所有路由都匹配不上的时候,就匹配Redirect中的路由
使用:Redirect在所有Route标签之后使用,利用to属性指明路由地址。

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

嵌套路由

嵌套路由就是在路由组件中再进行路由的导航和注册。
eg:效果:
React路由_第11张图片
新增和改动的页面:
Home/index.jsx

import React, { Component } from 'react'
import {Switch, Route, 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>
        <h3>我是Home组件</h3>
        <div>
          <ul class="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>
    )
  }
}

Home/News/index.jsx

import React, { Component } from 'react'

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

Home/Message/index.jsx

import React, { Component } from 'react'

export default class Message extends Component {
  render() {
    return (
    <div>
        <ul>
          <li>
            <a href="/message1">message001</a>&nbsp;&nbsp;
          </li>
          <li>
            <a href="/message2">message002</a>&nbsp;&nbsp;
          </li>
          <li>
            <a href="/message/3">message003</a>&nbsp;&nbsp;
          </li>
        </ul>
      </div>
    )
  }
}

Home/index.js中的 News匹配的路由就是二级路由,注意这里的路由匹配规则不是直接匹配。所有的路由匹配会按照注册路由的顺序进行匹配, News会先匹配App.jsx中的注册路由,匹配到了之后再去Home组件匹配注册路由,当匹配到了就在页面进行显示 。

Home/index.js中的, 就是设置当只匹配到了/home, 后面的路径未匹配时在Home页面默认显示的内容。

总结:

  • 注册子路由是需要写上父路由的path值
  • 路由的匹配是按照注册路由的顺序进行的

路由传参

向路由组件传递params参数

使用方法
  • 向路由组件传递params参数 (在路径后面直接接参数)
 <Link to={`/home/message/detail/001/消息1`}>{message.title}</Link>
  • 注册路由时,接收路由组件传递的params参数 (在路径后面使用 :参数名 进行接收)
<Route path="/home/message/detail/:id/:title" component={Detail}/>
  • 在路由组件中接收传递过来的的参数并使用
    路由传递的参数存储在this.props.match.params 中:
render() {
	const {id, title} = this.props.match.params
}
例子

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:'001', title:'消息1'},
    {id:'002', title:'消息2'},
    {id:'003', title:'消息3'}
   ]   
  }
  render() {
    const {messageArr} = this.state
    return (
    <div>
        <ul>
          {
            messageArr.map((message) => {
              return (
                <li key={message.id}>
                  {/* 向路由组件传递params参数 */}
                  <Link to={`/home/message/detail/${message.id}/${message.title}`}>{message.title}</Link>&nbsp;&nbsp;
                </li>
              )
            })
          }
        </ul>
        <hr/>
        {/* 注册路由 */}
        {/* 接收路由组件传递的params参数 */}
        <Route path="/home/message/detail/:id/:title" component={Detail}/>
      </div>
    )
  }
}

Detail/index.jsx

import React, { Component } from 'react'

export default class Detail extends Component {
  state = {
    dataDetail:[
        {id: '001', context: '我是消息1的内容'},
        {id: '002', context: '我是消息2的内容'},
        {id: '003', context: '我是消息3的内容'},
    ]
  }
  render() {
    const {id, title} = this.props.match.params
    const {dataDetail} = this.state 
    const findDetail = dataDetail.find((detailObj) => {
        return detailObj.id === id
    })
    return (
      <div>
        <div>ID: {id}</div>
        <div>TITLE: {title}</div>
        <div>Context:{findDetail.context}</div>
      </div>
    )
  }
}

React路由_第12张图片

向路由组件传递search参数(query参数)

补充:qs库
  • qs库 可以使用stringify()方法将 对象格式转换为urlencoded编码格式 (key=value&key=value)
    eg:
import qs from 'qs'

let obj = {name:'yang', age:20}
// 转成 urlencoded格式 (key=value&key=value)
console.log(qs.stringify(obj));

输出:

name=yang&age=20
  • qs库 可以使用parse()方法将urlencoded编码格式 转换为对象格式
console.log(qs.parse('name=yang&age=20'))

输出:

{name: 'yang', age: '20'}
使用方式
  • 向路由组件传递search参数 (在路径后面直接接参数)
 <Link to={`/home/message/detail?id=${message.id}&title=${message.title}`}>{message.title}</Link>
  • 注册路由时,无需接收search参数
<Route path="/home/message/detail" component={Detail}/>
  • 在路由组件中接收传递过来的的参数并使用
    路由传递的参数存储在this.props.location.search 中:
    但是还参数是未解析的状态,即urlencoded编码格式,需要借助qs库进行解析
  render() {
	    // 接收search参数
	    const {search}  = this.props.location
	    // slice(1)是为了去掉参数传递时的问号
	    const {id, title} = qs.parse(search.slice(1))
    }
例子

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:'001', title:'消息1'},
    {id:'002', title:'消息2'},
    {id:'003', title:'消息3'}
   ]   
  }
  render() {
    const {messageArr} = this.state
    return (
    <div>
        <ul>
          {
            messageArr.map((message) => {
              return (
                <li key={message.id}>
                  {/* 向路由组件传递params参数 */}
                  {/* {message.title}   */}

                  {/* 向路由组件传递search参数 */}
                  <Link to={`/home/message/detail/?id=${message.id}&title=${message.title}`}>{message.title}</Link>&nbsp;&nbsp;
                </li>
              )
            })
          }
        </ul>
        <hr/>
        {/* 注册路由 */}
        {/* 接收路由组件传递的params参数 */}
        {/*  */}

        {/* search参数无需接受 */}
        <Route path="/home/message/detail" component={Detail}/>
      </div>
    )
  }
}

Detail/index.jsx

import React, { Component } from 'react'
import qs from 'qs'

let obj = {name:'yang', age:20}
// 转成 urlencoded格式 (key=value&key=value)
console.log(qs.stringify(obj));

console.log(qs.parse('name=yang&age=20'))

export default class Detail extends Component {
  state = {
    dataDetail:[
        {id: '001', context: '我是消息1的内容'},
        {id: '002', context: '我是消息2的内容'},
        {id: '003', context: '我是消息3的内容'},
    ]
  }
  render() {
    // 接收params参数
    // const {id, title} = this.props.match.params

    // 接收search参数
    const {search}  = this.props.location
    const {id, title} = qs.parse(search.slice(1))


    const {dataDetail} = this.state 
    const findDetail = dataDetail.find((detailObj) => {
        return detailObj.id === id
    })
    return (
      <div>
        <div>ID: {id}</div>
        <div>TITLE: {title}</div>
        <div>Context:{findDetail.context}</div>
      </div>
    )
  }
}

向路由组件传递state参数

params参数和search参数都会显示在地址栏中,但是state参数就不会显示在地址栏上

使用方式
  • 向路由组件传递state参数 (在路径后面直接接参数)
<Link to={{pathname:'/home/message/detail', state:{id:message.id, title:message.title }}}>{message.title}</Link>
  • 注册路由时,无需接收state参数
<Route path="/home/message/detail" component={Detail}/>
  • 在路由组件中接收传递过来的的参数并使用
    路由传递的参数存储在this.props.location.state 中,是对象格式
  render() {
	     const {id, title} = this.props.location.state 
    }
例子

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:'001', title:'消息1'},
    {id:'002', title:'消息2'},
    {id:'003', title:'消息3'}
   ]   
  }
  render() {
    const {messageArr} = this.state
    return (
    <div>
        <ul>
          {
            messageArr.map((message) => {
              return (
                <li key={message.id}>
                  {/* 向路由组件传递params参数 */}
                  {/* {message.title}   */}

                  {/* 向路由组件传递search参数 */}
                  {/* {message.title}   */}

                  {/* 向路由传递state参数 */}
                  <Link to={{pathname:'/home/message/detail', state:{id:message.id, title:message.title }}}>{message.title}</Link>&nbsp;&nbsp;
                </li>
              )
            })
          }
        </ul>
        <hr/>
        {/* 注册路由 */}
        {/* 接收路由组件传递的params参数 */}
        {/*  */}

        {/* search参数无需接受 */}
        {/*  */}

        {/* state参数无需接受 */}
        <Route path="/home/message/detail" component={Detail}/>
      </div>
    )
  }
}

Message/Detail/index.jsx

import React, { Component } from 'react'
import qs from 'qs'

let obj = {name:'yang', age:20}
// 转成 urlencoded格式 (key=value&key=value)
console.log(qs.stringify(obj));

console.log(qs.parse('name=yang&age=20'))

export default class Detail extends Component {
  state = {
    dataDetail:[
        {id: '001', context: '我是消息1的内容'},
        {id: '002', context: '我是消息2的内容'},
        {id: '003', context: '我是消息3的内容'},
    ]
  }
  render() {
    // 接收params参数
    // const {id, title} = this.props.match.params

    // 接收search参数
    // const {search}  = this.props.location
    // const {id, title} = qs.parse(search.slice(1))

    // 接收state参数
    const {id, title} = this.props.location.state || {}


    const {dataDetail} = this.state 
    const findDetail = dataDetail.find((detailObj) => {
        return detailObj.id === id
    }) || {}
    return (
      <div>
        <div>ID: {id}</div>
        <div>TITLE: {title}</div>
        <div>Context:{findDetail.context}</div>
      </div>
    )
  }
}

React路由_第13张图片
浏览器地址中没有相关参数信息,但是页面刷新的时候参数不会丢失因为state参数存储在history的location的state中。
state参数是存储在路由组件的history的location的state中的,所以当浏览器的缓存被清空,history就被清空,参数就会丢失,再次访问该地址就无法获取参数就会报错。

路由的两种模式push与replace

浏览器的history会存储我们访问的路径,存储结构是栈结构,存储模式有两种模式push模式和replace模式:

  • push模式:将新访问的路径直接压入栈
  • replace模式:用新访问的路径替换栈顶元素

默认是push模式,可以通过给Link标签添加replace属性即可:

<Link replace={true} to={{pathname:'/home/message/detail', state:{id:message.id, title:message.title }}}>{message.title}</Link>

编程式路由导航

可以不通过Link标签来实现路由导航的路由是编程时路由导航,实际上是操作history身上的API。
注意:只有路由组件才有.props.history属性,才能直接使用如下变成式路由导航
this.props.history的API:

  • replace(跳转路径): replace路由跳转
  • push(跳转路径):push路由跳转
  • go(n):前进或后退|n|步
  • goBack():后退1步
  • goForward():前进1步

replace和push方法可以携带不同的参数(param、query、state)进行路由跳转。
replace跳转

   // replace + params参数
	 this.props.history.replace(`/home/message/detail/${id}/${title}`)
    // replace + query参数
     this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`)
     // replace + state参数
	 this.props.history.replace(`/home/message/detail`, {id, title})

push跳转

	 // push + params参数
   	this.props.history.push(`/home/message/detail/${id}/${title}`)
    // push + query参数
  	this.props.history.push(`/home/message/detail?id=${id}&title=${title}`)
    // push + state参数
    this.props.history.push(`/home/message/detail`, {id, title})

eg:

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:'001', title:'消息1'},
    {id:'002', title:'消息2'},
    {id:'003', title:'消息3'}
   ]   
  }
  replaceShow = (id, title)=>{
    // replace + params参数
    // this.props.history.replace(`/home/message/detail/${id}/${title}`)
    // replace + query参数
    // 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 + query参数
    // this.props.history.push(`/home/message/detail?id=${id}&title=${title}`)
    // push + state参数
    this.props.history.push(`/home/message/detail`, {id, title})
  }
  back = ()=>{
    this.props.history.goBack()
  }
  forward = ()=>{
    this.props.history.goForward()
  }
  go = ()=>{
    this.props.history.go(2)
  }
  render() {
    const {messageArr} = this.state
    return (
    <div>
        <ul>
          {
            messageArr.map((message) => {
              return (
                <li key={message.id}>
                  {/* 向路由组件传递params参数 */}
                  {/* {message.title}   */}
                  
                  {/* 向路由组件传递search(query)参数 */}
                  {/* {message.title}   */}

                  {/* 向路由传递state参数 */}
                  <Link to={{pathname:'/home/message/detail', state:{id:message.id, title:message.title }}}>{message.title}</Link>&nbsp;&nbsp;

                  <button onClick={()=>this.pushShow(message.id, message.title)}>push查看</button>&nbsp;&nbsp;
                  <button onClick={()=>this.replaceShow(message.id, message.title)} >replace查看</button>
                </li>
              )
            })
          }
        </ul>
        <hr/>
        {/* 注册路由 */}
        {/* 接收路由组件传递的params参数 */}
        {/*  */}

        {/* search参数无需接受 */}
        {/*  */}

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

        <button onClick={this.back}>后退</button>
        <button onClick={this.forward}>前进</button>
        <button onClick={this.go}>前进2</button>

      </div>
    )
  }
}

message/detail/index.jsx
不同的参数接收方式也要做出相应的改变

import React, { Component } from 'react'
import qs from 'qs'

let obj = {name:'yang', age:20}
// 转成 urlencoded格式 (key=value&key=value)
console.log(qs.stringify(obj));

console.log(qs.parse('name=yang&age=20'))

export default class Detail extends Component {
  state = {
    dataDetail:[
        {id: '001', context: '我是消息1的内容'},
        {id: '002', context: '我是消息2的内容'},
        {id: '003', context: '我是消息3的内容'},
    ]
  }
  render() {
    // 接收params参数
    // const {id, title} = this.props.match.params

    // 接收search参数
    // const {search}  = this.props.location
    // const {id, title} = qs.parse(search.slice(1))

    // 接收state参数
    const {id, title} = this.props.location.state || {}


    const {dataDetail} = this.state 
    const findDetail = dataDetail.find((detailObj) => {
        return detailObj.id === id
    }) || {}
    return (
      <div>
        <div>ID: {id}</div>
        <div>TITLE: {title}</div>
        <div>Context:{findDetail.context}</div>
      </div>
    )
  }
}

React路由_第14张图片

withRouter的使用(一般组件实现编程式路由导航)

注意:由于一般组件身上没有.props.history属性,但是如果还是想要实现编程式路由导航可以借助withRouter实现
withRouter可以加工一般组件,让一般组件具备路由组件特有的API(props.history等)
withRouter的返回值是一个带有路由组件API的新组件。
使用:
Header/index.jsx(Header组件是一个一般组件)

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() {
    return (
        <div className="row">
            <div className="col-xs-offset-2 col-xs-8">
                <div className="page-header">
                  <h2>React Router Demo</h2>
                  <button onClick={this.back}>后退</button>
                  <button onClick={this.forward}>前进</button>
                  <button onClick={this.go}>前进2</button>
                </div>
            </div>
        </div>
    )
  }
}

export default withRouter(Header)
// withRouter可以加工一般组件,让一般组件具备路由组件特有的API(props.history等)

BrowserRouter 和 HashRouter的区别

  1. 底层原理不一样:
    BrowserRouter使用的是H5的history API(注意不是组件实例身上的history),不兼容IE9及以下版本。
    HashRouter使用的是URL的哈希值
  2. url表现形式不一样
    BrowserRouter的路径中没有#,例如: localhost:3000/demo/test
    HashRouter的路径包含#,例如: localhost:3000/#/demo/test
  3. 刷新后对路由state参数的影响
    因为state保存在history对象中,页面刷新并不会关闭浏览器,有记忆功能,所以BrowserRouter没有任何影响
    但是HashRouter刷新后会导致路由state参数的丢失。
  4. 备注: HashRouter可以用于解决一些路径错误相关的问题。HashRouter不会将URL的哈希值(即#后面的值)传给服务器。

你可能感兴趣的:(React,react.js,前端,javascript)