react之 初识路由

2022-03-15
6 min read
Featured Image

SPA

  1. 单页面应用 Single Page Application
  2. 整个应用只有一个完整的页面
  3. 点击页面中的连接不会刷新页面, 只会做页面的局部刷新
  4. 数据都用 Ajax 请求, 异步

路由 router

  1. 路由是一个映射关系 (key: value)
  2. key 就是地址栏的 path, value 就是对应的组件或者 function

路由分类

后端路由

  1. 理解: value是function, 用来处理客户端提交的请求。

  2. 注册路由: router.get(path, function(req, res))

  3. 工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据

前端路由

  1. 浏览器端路由,value是component,用于展示页面内容。

  2. 注册路由: <Route path="/test" component={Test}>

  3. 工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件

前端路由基本使用

  1. 先安装 yarn add react-router-dom@5 , 此处安装的是 5 版本, 用来理解和学习, 后续会补充 6 版本的安装和使用.

  2. 编写路由链接:

    1. 首先引入 import {Link} from 'react-router-dom'
    2. 在需要点击跳转的地方编写路由链接
    {/* 原生 js 靠 a 标签路由 */}
    {/* <a className="list-group-item" href="./about.html">
      About
    </a>
    <a className="list-group-item active" href="./home.html">
      Home
    </a> */}
    {/* 编写路由链接 */}
    <Link className="list-group-item" to="/about">
      About
    </Link>
    <Link className="list-group-item" to="/home">
      Home
    </Link>
    
  3. 注册路由:

    1. 首先引入 import {Route} from 'react-router-dom'
    2. 在需要渲染地方注册路由
    {/* 注册路由 */}
      <Route path="/about" component={About}/>
      <Route path="/home" component={Home}/>
    
  4. 在 index.jsx 中引入 import {BrowserRouter} from 'react-router-dom'

    1. 原因是 <Link/><Route/> 都需要被 <BrowserRouter/> 来包裹, 所以写在 index.js 中相当于包裹整个项目.
    ReactDOM.render(
      <React.StrictMode>
        <BrowserRouter>
        <App />
        </BrowserRouter>
      </React.StrictMode>,
      document.getElementById('root')
    );
    

路由组件与普通组件

普通组件

  1. 放在 component 文件夹内
  2. props 不传就没有东西
  3. <Demo/>

路由组件对象的内容

  1. 放在 pages 文件夹内

  2. 不点击就不挂载

  3. <Route path="/about" component={Demo}/>

  4. props 不传也有东西

    {{
        "history": {
    				go: f go(n)
            goBack: f,
            goForward: f,
            push: f push(path,state)
            replace: f replace(path,state)
        },
        "location": {
            "pathname": "/about",
            "search": "",
            "hash": "",
            "key": "p7l20g",
             state: undefined
        },
        "match": {
          	params: {},
            "path": "/about",
            "url": "/about",
        }
    }
    

基本使用

  1. Link 标签作为导航并且需要高亮时, 会不太方便. 用 NavLink 标签的好处是, 当点击这个路由标签后, 会自动给标签追加一个 active 类, 因此可以通过 CSS 来改变标签的样式.

  2. 这里有一个巧合就是 Bootstrap 的高亮显示类也是 active , 所以使用起来非常方便.

  3. 如果说使用的 CSS UI 库对应的高亮显示类不是 active 就可以在 NavLink 内来单独设置高亮显示的类名. 比如高亮显示类名是highlight , 就可以写 activeClassName="highlight".

    <NavLink className="list-group-item" to="/about">About</Link>
    <NavLink className="list-group-item" to="/home">Home</Link>
    
  4. 如果说, 想要单独设置高亮颜色, 绕过已经引入的 UI 库, 可以在 CSS 内设置!important 语法.

    .active {
      background-color: yellow !important;
      color: red !important
    }
    

二次封装

  1. 由于在书写 NavLink 标签的时候, 有大量标签属性重复, 比如 className, activeClassName 或者其他自定义信息, 所以可以进行二次封装来减少代码重复性.

  2. 创建一个新的一般组件 MyNavLink , 需要引入import { NavLink } from "react-router-dom", 同时渲染NavLink. 通过 {...this.props} 来把 MyNavLink 收到的 props 传到 NavLink 的 props 中去.

    render() {
      return (
        <NavLink className="list-group-item" {...this.props} />
      )
    }
    
  3. 在实际挂载 MyNavLink 的时候, 通过标签内的属性来给组件传递 props. 这里注意: 标签内容也会作为 props 中的 children 传递到组件中去. 传递后, NavLink 会将收到的 props.chidren 渲染到生成的标签内容.

    • MyNavLink 标签内容 -> MyNavLink.props.children -> NavLink.props.Children -> NavLinks标签内容
  4. 这样的好处就是, 二次封装后, MyNavLink 组件依然可以写成标签➕闭合标签的形式, 并且在标签内容内写需要的内容, 从而与 NavLink呼应.

Switch 的作用

注册路由的时候, 可以引入 import {Switch} from "react-router-dom" 包裹在所有 <Route> 标签的外侧.

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

这样可以避免一种情况:

​ 相同地址下, 如果多传了一个路由组件建, 会同时显示两个组件.

样式丢失的解决

  1. 在 react 服务器下, 如果访问一个不存在的地址, 或者是前端路由地址, react 会默认返回 public 中的 index.html 页面. 这个时候会出现样式丢失的情况.

  2. 发生这种情况的原因是:

    1. 在 index.html 中引入的 css 路径如下:

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

    2. 正常情况下加载 css 文件是从根目录开始的:

      http://localhost:3000/css/bootstrap.css

    3. 而直接输入不存在地址,或者是前端路由地址, 加载 css 是从路由地址开始的:

      http://localhost:3000/notexist/css/bootstrap.css

    4. localhost: 3000 对应的是 pubic 文件夹. 而 public 文件夹中并不存在 notexist 文件夹. 所以 css 会加载失败.

  3. 解决办法:

    1. 更改 css 引入路径为绝对路径
    <link rel="stylesheet" href="/css/bootstrap.css" />
    或者
    <link rel="manifest" href="%PUBLIC_URL%/css/bootstrap.css" />
    
    1. 改 BrowserRouter 为 HashRouter

路由的模糊匹配和精准匹配

模糊匹配

如果编写的路由链接大于或者说包含注册路由, 则跳转到包含的注册路由.

{/* 编写路由链接 */}
<MyNavLink to="/fanxuan/about">
  About
</MyNavLink>
<MyNavLink to="/fanxuan/home/a/b">
  Home
</MyNavLink>
{/* 注册路由 */}
<Switch>
  <Route path="/fanxuan/about" component={About} />
  <Route path="/fanxuan/home" component={Home} />
</Switch>

精准匹配

正常来说不开启精准匹配, 有时候开启无法继续匹配二级路由.

{/* 编写路由链接 */}
<MyNavLink to="/fanxuan/about">
  About
</MyNavLink>
<MyNavLink to="/fanxuan/home/a/b">
  Home
</MyNavLink>
{/* 注册路由 */}
<Switch>
  <Route exact path="/fanxuan/about" component={About} />
  <Route exact path="/fanxuan/home" component={Home} />
</Switch>

Rediect 的使用

在没有匹配到任何路由链接时, 比如直接输入网站地址需要跳转的情况下. 就可以设置 <Redirect> 标签进行跳转.

{/* 注册路由 */}
<Switch>
  <Route path="/about" component={About} />
  <Route path="/home" component={Home} />
  <Redirect to="/about"/>
</Switch>

路由的嵌套

  1. 路由的注册的是有先后顺序的, App.jsx 中的路由先注册, 先生效.

  2. 二级路由渲染页面的时候, 需要渲染一级路由的组件, 再渲染二级路由的组件. 一级路由被渲染的原因是出发了路由的模糊匹配.

  3. 做默认显示的时候, 可以用 <Redirect> 来匹配页面. 挂载了二级路由后, 发现二级路由为空, 则匹配<Redirect>.

一级路由

{/* 编写路由链接 */}
<MyNavLink to="/about" >
  About
</MyNavLink>
<MyNavLink to="/home">
  Home
</MyNavLink>

{/* 注册路由 */}
<Switch>
  <Route path="/about" component={About} />
  <Route path="/home" component={Home} />
  <Redirect to="/about" />
</Switch>

二级路由

<ul className="nav nav-tabs">
    <li>
      <MyNavLink to="/home/news"> News </MyNavLink>
    </li>
    <li>
      <MyNavLink to="/home/messages"> Messages </MyNavLink>
    </li>
  </ul>
  <Switch>
    <Route path="/home/messages" component={Messages} />
    <Route path="/home/news" component={News} />
    <Redirect to="/home/news" /> 
  </Switch>
</div>

向路由组件传递参数

向路由组件传递 params 参数

  1. 传递
{/* 向路由组建传递 params 参数 */}
<Link to={`/home/messages/details/${message.id}/${message.title}`}>
  {message.title}
</Link>
  1. 声明接收
{/*  声明接收 params 参数 */}
<Route path="/home/messages/details/:id/:title" component={Details} />
  1. 接收
//接收 params 参数
const {id, title} = this.props.match.params

向路由组件传递 serach 参数

  1. 传递
{/* 向路由组建传递 serach 参数 */}
<Link to={`/home/messages/details/?id=${message.id}&title=${message.title}`}>
{message.title}
</Link>
  1. 无需声明接收
{/*  search 参数无需声明接收 */}
<Route path="/home/messages/details" component={Details} />
  1. 接收
    1. 由于接收到的参数形式是 urlencoded 也就是 querystring 形式 : key=value&key=value: urlencoded, 所以为了方便, 可以引入一个 react 自带的库来转化成对象.
    2. 引入 import qs from 'qs', 主要的两个方法 qs.Stringify(obj): 对象转 qs, qs.Parse(qs.slice(1)): qs转对象.
const {search} = this.props.location
const result = qs.parse(search.slice(1))
const {id, title} = result
const findRes = DetialData.find((item) => item.id === id)

向路由组建传递 state 参数

不会在地址中显示穿的参数, 但刷新页面, 依然带着参数. 如果删除缓存, state 变为 undefined.

  1. 传递
{/* 向路由组建传递 state 参数 */}
<Link to={{pathname:'/home/messages/details',state:{id:message.id,title:message.title}}}>
	{message.title}
</Link>
  1. 声明接收
{/*  state 参数无需声明接收 */}
<Route path="/home/messages/details" component={Details} />
  1. 接收
const {id, title} = this.props.location.state
const findRes = DetialData.find((item) => item.id === id)

push与 replace 的区别

在编写路由连接的时候, 可以加 replace=true 来标识路由为 replace 模式. replace 模式不会将访问存在于历史记录, 后退函数也拿不到历史记录. 历史记录类似于一个栈结构, replace 是直接换栈顶的内容.

//replace=true 可直接简写为 true
<MyNavLink replace to="/home/messages"> Messages </MyNavLink>

编程式路由导航

编程式就是直接调用 props.history 中的函数api前进或者后退.

pushClick = (id,title) => {
    this.props.history.push('/home/messages/details',{ id, title })
  }
  replaceClick = (id,title) => {
    this.props.history.replace('/home/messages/details',{ id, title })
  }
  forward = () => {
    this.props.history.goForward()
  }
  back = () => {
    this.props.history.goBack()
  }
  go = () => {
    this.props.history.go(2)
  }
  render() {
    const { messagesArray } = this.state
    return (
      <div>
        <ul>
          {messagesArray.map((message) => {
            return (
              <li key={message.id}>
                {/* 向路由组建传递 state 参数 */}
                <Link to={{ pathname: '/home/messages/details', state: { id: message.id, title: message.title } }}>
                  {message.title}</Link>
                <button onClick={() => { this.pushClick(message.id, message.title) }}>push</button>
                <button onClick={() => { this.replaceClick(message.id, message.title) }}>replace</button>
              </li>
            )
          })}
        </ul>
        <hr />
        {/*  state 参数无需声明接收 */}
        <Route path="/home/messages/details" component={Details} />
        <button onClick={this.back}> 回退</button>
        <button onClick={this.forward}> 前进 </button>
        <button onClick={this.go}> 前进两页 </button>
      </div>
    )
  }
}

withRouter 的使用

withRouter 函数可以接收一个一般组件, 将其转化为带有路由属性的组件(props 里有 history,match,location)

import React, { Component } from "react"
import { withRouter } from "react-router-dom"
class Header extends Component {
  forward = () => {
    this.props.history.goForward()
  }
  back = () => {
    this.props.history.goBack()
  }
  go = () => {
    this.props.history.go(2)
  }
  render() {
    return (
      <div className="page-header">
        <button onClick={this.back}> 回退</button>
        <button onClick={this.forward}> 前进 </button>
        <button onClick={this.go}> 前进两页 </button>
        <h2>React Router Demo</h2>
      </div>
    )
  }
}
export default withRouter(Header)
Avatar
Aaron Fan My research interests include machine learning, signal processing, web development and robotics.