25、react 中使用路由 router 详解

react 中使用路由 router 详解

今天开始最新的一个模块,也是 react 开发中最重要的一部分,就是路由。

SPA 理解

我们使用原生的 HTML + CSS + JavaScript 开发的前端应用,是多页面的,每次点击一个链接跳转到不同的页面,其实就是实打实的去了一个新的页面。但是我们现在学习的 vue 也好,react 也好,他和原生的不一样,他们是 SPA。

  • 单页面应用(Single page web application,即 SPA
  • 整个应用只有一个完整的页面。
  • 点击页面中的链接不会刷新页面,只会做页面的局部更新
  • 数据都需要通过 Ajax 请求获取,并在前端异步展现。

简单点说,之前点击一个链接,直接打开一个新的 .html 页面展示数据。但是 react 是只有一个 html 页面,也就是你看到的,然后切换页面,还是这个 html,只是通过在 html 里面切换不同的组件,展示不同的内容。

什么是路由

  • 路由就是一个映射关系( key: value)。
  • key 为路径, value 可能是 function 或者是 component。

我们学习路由的时候会遇到两个经常用的单词 routerroute。区别是什么呢? router 是全局的路由器对象。route 是当前激活的路由对象。

打个比方哈,我们自己家里上网需要路由器吧,router 就相当于家里的 路由器。route 就相当于路由器上面的网线插口或者是那个天线,不能没有 router 就是用 route。

路由的分类

  • 后端路由:

    • 理解:value 是 function,用来处理客户端提交的请求。
    • 注册路由:router.get( path , function ( req , res ) )
    • 当 node 接收到一个请求的时候,根据请求路径找到匹配的路由,然后调用路由中的函数返回响应数据。
  • 前端路由:

    • 浏览器端路由,value 是 component,用于展示页面内容。
    • 注册路由:<Route path="/wjw" component={Wjw}>
    • 当浏览器的 path 变为 /wjw 时,当前路由组件就会被替换为 Wjw 组件。

react-router-dom

开发 react 项目中使用路由,需要使用一个第三方库,这个第三方库在脚手架创建项目的时候是没有自动给我们安装的,所以说,我们需要自行安装一下,我们先安装 5 版本的,因为我是用的 5 版本,其他版本不保证和我下面说的案例都能匹配起来哈。

  1. react-router-dom 是 react的一个插件库。
  2. 专门用来实现一个 SPA 应用。
  3. 基于 react 的项目基本都会用到此库。

安装的话使用命令安装:

npm install react-router-dom@5

安装完成,就可以使用 react-router-dom。

基本路由案例

我们还是以案例的方式来,做一个简单的案例,在做的过程中,就把基本的用法学了。

我们来做一个最简单的案例。

在这里插入图片描述
我们做一个超级简单的页面,里面首先是一个大标题,这个是写死的,简单的展示用。然后两个链接,点击链接的时候,浏览器地址的 path 会变化,下面是一个展示区域,点击按钮之后,展示不同的信息。

首先分析一波。

在这里插入图片描述

上面这个图片都能看懂哈,其实在经典的 react 项目中,一个页面就分了三部分。

在这里插入图片描述

  • 标题区域:主要用于展示系统信息。 系统名称、系统 logo、当前时间、用户信息等。
  • 导航栏区域:主要用于展示系统导航菜单。点击菜单项,内容区域展示不同数据。
  • 内容区域:主要用来展示信息。当导航栏点击了相应的菜单,内容区域展示相关页面。

所以说我们写的案例,上边绿色框里的大标题就是标题区域。中间蓝色框就是导航区域,里面有两个菜单,一个是 Home,一个是 About。最下面的黄色框就是内容区域,当导航栏点击了 Home 菜单就展示 Home 页面,点击 About 菜单就展示 About 组件。所以说,页面的切换,在 react 里面就是组件的切换。

编写代码

首先在黄色区域要有两个组件 Home 和 About 来实现点击导航菜单的 Home 和 About 来回切换吧?

所以说首先创建两个组件,分别是 Home 和 About ,里面啥都不需要,就展示一段话就可以了吧。

在这里插入图片描述

About 组件

import React, { Component } from 'react'

export default class About extends Component {
  render() {
    return (
      <h3>你好ed. 我是 About 组件</h3>
    )
  }
}

Home 组件

import React, { Component } from 'react'

export default class Home extends Component {
  render() {
    return (
      <h3>你好ed. 我是 Home 组件</h3>
    )
  }
}

OK,来回切换的组件编写完了,现在需要编写 App 组件了吧?

// 创建外壳组件
import React, { Component } from "react";
import Home from "./components/Home";
import About from "./components/About";

// 创建并暴露APP组件
export default class App extends Component {

  render() {
    return (
      <div>
        <h1>react router 学习</h1>

        <hr></hr>
        
		{/* 在原生中,靠 <a> 标签实现页面切换 */}
        <a href="./about">About</a>
        <a href="./home" style={{ marginLeft: '20px' }}>Home</a>

        <hr></hr>
        
        <Home />
        <About />
        
      </div>
    )
  }
}

OK,我们在最开始的话,是使用 <a> </a> 标签的方式,我们把 Home 和 About 组件全部引入进来看一下效果。

在这里插入图片描述

现在是两个组件都展示出来了,我们要想实现点击菜单切换不同组件,就要使用我们安装的 react-router-dom 库。写菜单的时候 就不要使用 a 标签了,那不用 a 标签用啥呢?这个库里面为我们提供了一个工具 Link

我们使用一下,首先要引入 import { Link } from 'react-router-dom' ,然后修改菜单。

// 创建外壳组件
import React, { Component } from "react";
import { Link } from 'react-router-dom'
import Home from "./components/Home";
import About from "./components/About";

// 创建并暴露APP组件
export default class App extends Component {

  render() {
    return (
      <div>
        <h1>react router 学习</h1>

        <hr></hr>
        
		{/* 在 react 中靠路由连接切换组件 —— 编写路由链接*/}
         <Link to="/about">About</Link>
         <Link to="/home" style={{ marginLeft: '20px' }}>Home</Link>

        <hr></hr>
        
        <Home />
        <About />
        
      </div>
    )
  }
}

其中 Link 标签里面的 to 是指前往哪个 path 路由。OK,写完这里如果成功了,我们在点击页面的时候浏览器地址栏会切换到不同的路径。

在这里插入图片描述

OK,保存完之后,浏览器控制台直接报错了。他说啥呢?他说你应该在 <Link> 标签外面套一个 <Router> 标签才可以。

OK,这有坑哈,我们先套上一个看看效果哈。

首先引入 Router。

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

然后给他包上一层。

        <Router>
          <Link to="/about">About</Link>
          <Link to="/home" style={{ marginLeft: '20px' }}>Home</Link>
        </Router>

刷新页面。

在这里插入图片描述

这又是为啥呢,其实最开始那个 应该在 <Link> 标签外面套一个 <Router> 标签 这个提示给的不好,他想说的其实不是单纯的 <Router>,这是很多人入坑。

他想要的是啥哈,其实 React 的路由实现啊,是基于浏览器 BOM 的 history 的,不能说基于,说没他有些地方还真不行,他有两种:一种是 browser history,一种是 hash history 。我这里可能说的不明白哈,也可能说的不对,我理解了,但是我嘴笨说不出来,反正不是单纯的 <Router>,而是有两种,一种是<BrowserRouter> 一种是 <HashRouter>。 我们暂时使用 <BrowserRouter> 标签,这个不是 hash 的那种。

同样也是先引用。

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

然后在包裹。

        {/* 在 react 中靠路由连接切换组件 —— 编写路由链接*/}
        <BrowserRouter>
          <Link to="/about">About</Link>
          <Link to="/home" style={{ marginLeft: '20px' }}>Home</Link>
        </BrowserRouter>

现在我们在查看一下页面效果。

在这里插入图片描述
OK,现在控制台不报错了,我们点击两个导航菜单,看到浏览器地址栏的 path 也会改变了。

现在我们的 两个组件都是引入进来的,所以点击的时候组件是不会动态切换的,然后我们需要实现一下点击不同的菜单显示不同的组件。

我们现在组件直接是摞在一起的。

        <Home />
        <About />

然后我们不能这样写了,需要 router 帮我们处理。改成下面的样子

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

我们使用了 <Route> 标签,所以也得引入一下。

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

然后 <Route> 标签 配置了一个 path ,这个 path 是和我们导航菜单 <Link>标签 的 to 是对应的!然后还配置了一个 component 这个 component 首字母是小写的哈,他是配置哪个组件的!

这两句话的意思是:

  • 当浏览器的 path 变成 /about 的时候,这里就渲染 About 组件。
  • 当浏览器的 path 变成 /home 的时候,这里就渲染 Home 组件。

怎么理解呢?这样,我们假装哈,假装 router 里面有一个人,我们在 About 菜单 Link 里面配置了 to="/about" 之后,然后这个人就开始上班了,当我们点击这个 About 菜单,因为这个菜单有 to="/about" ,浏览器地址就变成了 /about ,浏览器地址一变,就通知这个人说,浏览器地址变了,你要帮我渲染组件了,然后这个人就去找配置 path 同样是 /about<Route> 标签,找到标签设置的 component 属性一看,哦~,你要渲染的组件是 About,然后他去帮我们渲染 About 组件了。所以整个流程,我们只需要触发就可以了。当然 Home 也一样。

OK,保存,刷新,看一下效果。

在这里插入图片描述
OK,这个错我熟啊,还是缺 <Router> 标签 包裹啊,那我给 <Route> 标签外边也包裹一个 <BrowserRouter> 标签,保存刷新!

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

我们看不报错了,最开始,后边既没有 /home 也没有 /about,所以说啥也没显示对吧?但是我们点击菜单,浏览器地址变化了,渲染的页面丝毫没有显示,更没有切换。

在这里插入图片描述

为啥呢?分析一下。我直接截取代码截图了哈。

在这里插入图片描述

看懂图了吗?我们想使用 导航控制组件显隐切换,但是我们用了两个路由器包裹,他们两个相互独立,而且两者互不干扰,你切换第一个的,第二个不搭理我们很正常吧!

那怎么办?很简单哈,用一个全包裹起来就可以了呗!

  render() {
    return (
      <div>
        <h1>react router 学习</h1>

        <hr></hr>

        {/* 在 react 中靠路由连接切换组件 —— 编写路由链接*/}
        <BrowserRouter>
          <Link to="/about">About</Link>
          <Link to="/home" style={{ marginLeft: '20px' }}>Home</Link>

          <hr></hr>

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

      </div>
    )
  }

我们直接用一个 <BrowserRouter> 标签把导航菜单和内容全部包裹起来,不就可以啦嘛!

在这里插入图片描述

OK,想一下这样样一件事情,如果我们这个 App 组件内容很多,各种东西,比如我们在有一个需要包裹,再有十个需要包裹,怎么办?那还不简单嘛,复制一下 <BrowserRouter> 标签,往外扩。当然这是没有问题的,但是每次都很麻烦是吧!

我们需要注意一个问题哈,就是在一个 React 项目里面,一般都只需要一个 路由器 吧。也就是说,所有的路由配置,无论是导航还是内容 ,肯定包含在一个 路由器 下面。所以说从根上解决问题。

我们找到 index.js 文件,直接在挂载整个 App 组件到页面的地方包裹可以吧?

// 引入 react 核心库
import React from 'react';

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

const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(
    <BrowserRouter>
      <App />
    </BrowserRouter>
);

好的,保存,刷新,效果完全一样。

在这里插入图片描述

OK哈,我们在继续来探讨,我们之前说,如果是原生的实现页面跳转是使用的 <a> 标签操作的,那么我们在 React 中使用的是 <Link> 标签,那我们看一下我们页面,最终是什么样子。

在这里插入图片描述

我们看到,最后浏览器渲染的还是把 <Line> 标签给渲染成了 <a> 标签,把 to 属性,渲染成了 href 属性。

我们之前说过,除了 <BrowserRouter> 还可以使用 <HashRouter>,我们同样可以再项目试一下,首先 <BrowserRouter> 我们见过了,浏览器的地址注意一下。
在这里插入图片描述
当我们一会切换 <HashRouter> 之后,我们看一下效果,在看一下浏览器地址。

// 引入 react 核心库
import React from 'react';

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

const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(
    <HashRouter>
      <App />
    </HashRouter>
);

效果是一样的,注意浏览器地址。

在这里插入图片描述
效果不用看了,是一样的,主要是浏览器地址,多了一个 # 号是吧?我们发现点了菜单,所谓的 /home/about 都在 # 号后面对吧,# 号后边的其实就是 hash 值,或者说是锚点值,特点就是 # 号后面的东西都不会作为资源发送给服务器。

好了,关于 React 的 Router 基本的使用就到这里了,下面部分我们继续深入。

中途总结 - 路由基本使用

  • 明确好界面中的导航部分、展示部分都是哪里。
  • 导航部分的 a 标签,改为 Link 标签。
    • <Link to="xxx">我是ed.</Link>
  • 展示部分写 Route 标签进行路径匹配。
    • <Route path="/xxx" component={Demo} />
  • <App> 的最外侧包裹一个 <BrowsweRouter> 或者 <HashRouter>

【本部分相关代码资料】:我是𝒆𝒅. 的 gitee

路由组件 与 一般组件

首先简单说一下什么是一般组件。一般组件我们写过好多了,就比如说这个案例的 Home 组件,我们在引入的时候应该怎么写呢?是下面这样子吧?

<Home />  

这就是使用 Home 组件吧?把组件作为标签写进来,这时候 Home 就是一般组件。

那什么是路由组件呢?之前案例有这么一句话。

<Link to="/home" style={{ marginLeft: '20px' }}>Home</Link>

我们没有直接使用 Home 组件吧?我们使用过 <Link> 来进行路由匹配,当匹配到了,然后他会给我们渲染 Home 组件吧?这时候 Home 就是路由组件。

所以按照这个说法来看,案例上面 Home 组件 和 About 组件都属于路由组件吧?我们把 Home 和 About 放到哪里了? components 文件夹里面了吧?所以说这么做其实不规范,因为 components 文件夹是用来放置一般组件的,如果是路由组件需要放置在 pages 文件夹下。

在这里插入图片描述

就是换了一下文件夹的名称,效果完全一样,记得把 App 组件引入的文件位置改一下就可以了。再说明一下,其实文件夹叫什么并无所谓,只要引入的路径对,就不影响程序的执行,这么做,只是为了标准化工程化的开发。

那我们把案例的标题拆成一般组件可以吧,这是后一般组件就是放到 components 文件夹下了吧?

在这里插入图片描述

那我们编写一下 Header 组件,其实很简单,就一句话。

import React, { Component } from 'react'

export default class Header extends Component {
    render() {
        return (
            <h1>react router 学习</h1>
        )
    }
}

然后在 App 组件引入一下就可以了。

// 创建外壳组件
import React, { Component } from "react";
import { Link, Route } from 'react-router-dom'
import Home from "./pages/Home";
import About from "./pages/About";
import Header from "./components/Header";

// 创建并暴露APP组件
export default class App extends Component {

  render() {
    return (
      <div>
        <Header />
        <hr></hr>
        {/* 在 react 中靠路由连接切换组件 —— 编写路由链接*/}
        <Link to="/about">About</Link>
        <Link to="/home" style={{ marginLeft: '20px' }}>Home</Link>
        <hr></hr>
        {/* 注册路由 */}
        <Route path="/about" component={About} />
        <Route path="/home" component={Home} />
      </div>
    )
  }
}

效果完全一样。

在这里插入图片描述

一般组件路由组件 区别

我们知道了 一般组件 和 路由组件 区别是 1. 存放的位置不同,2. 引入的方式不同 这两个区别的,对,但是不全对,其实他们还有一个很大的区别:props 的值。一般组件的 props 是多少呢?我们传递给他的是啥就是啥,我们不传,他就是空。但是路由组件,我们不传,他依旧有一些特定的数据会被接收到。看下面例子:

一般组件,我们不传递就是空,如果我们传递了,就是我们传递的值,我们给 Header 组件传递一个 name:

<Header name="我是ed." />

然后在 Header 组件里面打印一下:

render() {
        console.log("Header组件接收到的props:", this.props)
        return (
            <h1>react router 学习</h1>
        )
    }

看结果:

在这里插入图片描述

但是,我们没有给 Home 组件传值,我们在 Home 组件输出一下 props:

import React, { Component } from 'react'

export default class Home extends Component {
  render() {
    console.log("Home 组件接受到的props:", this.props)
    return (
      <h3>你好ed. 我是 Home 组件</h3>
    )
  }
}

点击 Home 菜单,切换出 Home 组件,看效果哈!

在这里插入图片描述
我们在路由组件,主要关注的是 historylocationmatch 这三个,这三个是前端开发经常用到的,具体是啥,我们在下面的介绍里面都会说到。

中途总结 - 一般组件与路由组件

  • 写法不同:
    • 一般组件: <Demo />
    • 路由组件:<Route path="/demo" component={Demo } />
  • 存放位置不同:
    • 一般组件:components 文件夹下。
    • 路由组件:pages 文件夹下。
  • 接收到的 props 不同:
    • 一般组件:写组件标签时传递了什么,就能收到什么。
    • 路由组件:接收到三个固定的属性值。
      • history:
        • go: f go(n)
        • goBack: f goBack()
        • goForward: f goForward()
        • push: f push(path, state)
        • replace: f replace(path, state)
      • location:
        • pathname: ‘/about’
        • search: ‘’
        • state: undefined
      • match:
        • params: { }
        • path: ‘/about’
        • url: ‘/about’

【本部分相关代码资料】:我是𝒆𝒅. 的 gitee

NavLink 的使用

上面案例我们基本已经实现了对吧?然后我们看一下之前案例的效果。

在这里插入图片描述
我们看,我们点击菜单完成之后,我们的菜单没有高亮,不看下面的内容,你不知道我们点击了什么菜单对吧?我想在我点击完菜单之后,对我点击的菜单设置一个红色的背景色表示当前选中菜单怎么办?当然我们自己写逻辑然后绑定是可以的,但是这是两个菜单写起来相对来说还是比较方便的,但是如果是十个菜单呢?是不是就比较麻烦了。

所以说,为了解决这样一个问题,router 工具为我们提供了另一个工具。

首先我们之前写菜单怎么写的? 下面这样子吧? 使用的 <Link> 标签。

<Link to="/about">About</Link>

为了满足我们样式的需求, router 给我们提供了一个 标签的升级版标签 —— <NavLink>

这玩意怎么用呢?其实用法超级简单,我们首先要引入,然后把 <Link> 标签替换成 <NavLink> 标签。我们修改一下子。

// 创建外壳组件
import React, { Component } from "react";
import { Route, NavLink } from 'react-router-dom'  // 引入 NavLink  
import Home from "./pages/Home";
import About from "./pages/About";
import Header from "./components/Header";

// 创建并暴露APP组件
export default class App extends Component {

  render() {
    return (
      <div>
        <Header name="我是ed." />
        <hr></hr>
        {/* 在 react 中靠路由连接切换组件 —— 编写路由链接*/}
		{/* 把之前写的 Link 标签替换成 NavLink */}
        <NavLink to="/about">About</NavLink>
        <NavLink to="/home" style={{ marginLeft: '20px' }}>Home</NavLink>
        <hr></hr>
        {/* 注册路由 */}
        <Route path="/about" component={About} />
        <Route path="/home" component={Home} />
      </div>
    )
  }
}

到这里,效果和之前做的案例是一模一样的,那他有什么作用呢?我们在给 index.html 文件添加一段样式之后,你在刷新看效果。

<style>
   .active {
     background-color: red;
   }
</style>

我们给 class 为 active 的标签设置了背景色为红色的样式,好的!刷新看效果。

在这里插入图片描述
我们发现点击选中标签之后,标签应用了我们在 index.html 文件设置的背景色。我们在看一下浏览器给我们渲染出来的标签。

在这里插入图片描述

所以说,当我们给导航菜单,使用 NavLink 标签之后,标签会自动给我们点击选中的菜单项标签添加一个 active 样式,所以说正好和我们设置的 CSS 样式匹配起来,实现选中高亮效果。

但是如果我们不想要这个添加的 class 样式名为 active 怎么办?非常好,NavLink 标签帮我们想到了,他里面有一个属性叫做 activeClassName,我们这个属性设置的值,就是我们想让他在选中后为我们设置的 class 名。比如说,下面我们设置一个新的样式名字:

<NavLink to="/about" activeClassName="aboutClass">About</NavLink>
<NavLink to="/home" activeClassName="homeClass"  style={{ marginLeft: '20px' }}>Home</NavLink>

同时这是一下每个名字对应的样式。

<style>
      .aboutClass {
        background-color: rgb(21, 120, 250);
      }

      .homeClass {
        background-color: greenyellow;
      }
</style>

效果也实现了呀!

在这里插入图片描述
好的这就是关于 NavLink 的基本使用。

【本部分相关代码资料】:我是𝒆𝒅. 的 gitee

NavLink 的封装

还是上面的案例,我们继续深入,我们上一部分使用 NavLink 实现了标签选中高亮效果对吧!好的,我们看代码,我们先把间距删掉哈,两个菜单就紧挨在一起了,无所谓了,然后我们两个标签都是用 aboutClass 样式,逻辑上没啥区别哈,就是为了方便讲解。

<NavLink to="/about" activeClassName="aboutClass">About</NavLink>
<NavLink to="/home" activeClassName="aboutClass">Home</NavLink>

OK,上面两行代码,定义了两个菜单,我们在每个菜单上设置了样式和 to 属性。假设,我们不止有两个菜单,假设10个,假设高亮的颜色都一样,怎么办?很简单复制十份就行了吧?那假设我们要给标签的样式超级超级多呢??一个一个加呗!那这样的话,我们的菜单会不会很繁琐啊?

所以说看图:
在这里插入图片描述

我们篮筐的数据都是一样的,我们能不能自己封装一个菜单项组件,我们添加菜单的时候就只需要调用我们自己写的组件,只传 to 就可以了。

OK,我们首先创建一个组件 MyNavLink 来封装一下 NavLink 标签,首先,这个组件是一个 一般组件 吧?所以放到 components 文件夹下。

在这里插入图片描述
我们编写组件内容,我们先简单复制一个 about 的菜单进来哈。

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

export default class MyNavLink extends Component {
  render() {
    return (
      <NavLink to="/about" activeClassName="aboutClass">About</NavLink>
    );
  }
}

我们修改一下 App 组件,引入我们自己封装的 MyNavLink 组件。

// 创建外壳组件
import React, { Component } from "react";
import { Route } from 'react-router-dom'
import Home from "./pages/Home";
import About from "./pages/About";
import Header from "./components/Header";
import MyNavLink from "./components/MyNavLink";

// 创建并暴露APP组件
export default class App extends Component {

  render() {
    return (
      <div>
        <Header name="我是ed." />
        <hr></hr>
        <MyNavLink/>
        <MyNavLink/>
        <hr></hr>
        {/* 注册路由 */}
        <Route path="/about" component={About} />
        <Route path="/home" component={Home} />
      </div>
    )
  }
}

我们看一下效果。

在这里插入图片描述
两个 About 菜单是对的哈,因为现在我们是写死的在自己封装的组件里面,因为高亮的样式是一样的,我们固定好就行,然后需要传递的就是 to 属性 和名字吧?我们之前学习过,使用 props 啊!所以我们直接实现一下。

首先是封装的 MyNavLink 组件:

  render() {
    let { title, to } = this.props
    console.log(this.props)
    return (
      <NavLink to={to} activeClassName="aboutClass">{title}</NavLink>
    );
  }

然后是 App 组件使用的地方传递参数:

<MyNavLink to="/about" title="About" />
<MyNavLink to="/home" title="Home" />

好的,再次看效果。

在这里插入图片描述
效果实现了吧?但是我们想一下,如果我们除了传入 to 属性、title 属性,还需要传递多个属性值呢?

那很简单吧,继续添加属性,其实呢,没必要。我们分析一波:我们在 App 组件传递的属性都是给谁用的?最后都是传给了 MyNavLink 组件里面的 NavLink 标签用吧?so…

我们在 MyNavLink 组件中:

<NavLink {...this.props} activeClassName="aboutClass">{title}</NavLink>

直接这样写可以吧?没问题吧?完全没问题,直接把 props 也就是传进来得值 拆分 进 NavLink 标签中传进去是吧,一点问题没有。

在这里插入图片描述
效果完全一样。

现在只剩下 title 看上去不是很和谐了吧?能不能把 title 也解决了呢?能!想一下,我们直接使用 NavLink 标签的时候是怎么使用的?

<NavLink to="/about" activeClassName="aboutClass">About</NavLink>

这样使用的吧?他不是自闭合的,我们把标签名作为标签体写进去的。但是我们引入的自己的组件怎么写的?

<MyNavLink to="/about" title="About" />

自闭合的吧?那我们能不能把我们自己的标签也写成非自闭合的,然后像使用 NavLink 一样把标签名作为标签体写进去呢?可以的。我们修改一下,这样子引入我们的自定义组件:

        <MyNavLink to="/about">About</MyNavLink>
        <MyNavLink to="/home">Home</MyNavLink>

好的,我们把标签名作为标签体写进去了,那 MyNavLink 怎么接受我们传进去的标签体呢?

【注意】标签体是一个特殊的标签属性,对应组件接收数据 props 中的 children。

我们改完,刷新页面看效果,主要是打印。

在这里插入图片描述
我们看到在 props 里面,多了一个 children 的属性值,这个不是我们传的,OK,这就是标签体内容啊!所以说我们在自定义组件修改一下就可以啦!

<NavLink {...this.props} activeClassName="aboutClass">{this.props.children}</NavLink>

保存看效果:

在这里插入图片描述
效果出来了吧!OK,再继续想,我们上面强调了,标签体是一个特殊的标签属性,对应组件接收数据 props 中的 children。 那我们需要给 NavLink 中设置标签体吗?不需要吧。我问:

<NavLink to="/about" children="About" activeClassName="aboutClass" /><NavLink to="/about" activeClassName="aboutClass">About</NavLink >

这两种写法一样不一样!答案是一样的!一模一样!

OK,所以最后我们使用自闭合直接传进去整个 props 就可以吧!

<NavLink {...this.props} activeClassName="aboutClass" />

OK,保存看效果。

在这里插入图片描述
OK,效果很完美,好了,这就是 NavLink 组件的封装,了解会用即可。

【本部分关键代码资料】:我是𝒆𝒅. 的 gitee

中途总结 - NavLink 与 NavLink 的封装

  • NavLink 可以实现路由链接高亮,通过设置 activeClassName 指定自定义样式名,默认 active。
  • 标签体内容是一个特殊的标签属性。
  • 通过 this.props.children 可以获取标签内容。

Switch 标签使用

继续上面的案例哈,继续深入宝子们!

我们在创建一个 路由组件 Test ,然后里面内容和 Home 组件、About 组件一模一样哈,就是输出的文字改一下,然后我们也是在 App 组件中引入一下,步骤都是一样的,关键是下面的代码!

        <Header name="我是ed." />
        <hr></hr>
        <MyNavLink to="/about">About</MyNavLink>
        <MyNavLink to="/home">Home</MyNavLink>
        <hr></hr>
        {/* 注册路由 */}
        <Route path="/about" component={About} />
        <Route path="/home" component={Home} />
        <Route path="/home" component={Test} />

我们还是只有两个菜单为 abouthome,但是组件有三个,但是注意了,/home 对应着两个组件,分别是 HomeTest。请问现在页面怎么渲染? about 菜单无所谓, 肯定渲染 About 组件,主要是 home 菜单怎么渲染?

  1. 只渲染第一个匹配到的,也就是 Home 组件
  2. 只渲染最后一个匹配到的,也就是 Test 组件
  3. 全部匹配,也就是 Home 组件和 Test 组件都渲染。

只有这三种情况吧?刷新页面看一下效果:

在这里插入图片描述
卧槽!无情!两个组件都渲染了。

所以说我们能够看出来,这个在匹配路由的时候,他是会按照顺序从头到尾挨个匹配对吧?不管之前有没有匹配到,只要匹配到了,我就给你渲染出组件,如果有100个组件,我只想匹配第一个,那后边的99个,还会不会去匹配?按这个效果看,还是会去匹配的吧?只不过判断之后没有匹配到,没返回罢了,但是还是挨个匹配。这就造成了一个很严重的效率问题吧?好的,怎么解决呢!成功的引出了我们这部分要说的一个标签 —— <Switch></Switch> 标签。

我们只需要简单的引入一下子。然后包裹一下我们的路由组件就可以啦!他在匹配到第一个路由组件之后,不管后边还能不能匹配到其他组件,我们都不管了,不向下匹配了,直接停止!奈斯!

// 创建外壳组件
import React, { Component } from "react";
import { Route, Switch } from 'react-router-dom'
import MyNavLink from "./components/MyNavLink";
import Header from "./components/Header";
import Home from "./pages/Home";
import About from "./pages/About";
import Test from './pages/Test'

// 创建并暴露APP组件
export default class App extends Component {

  render() {
    return (
      <div>
        <Header name="我是ed." />
        <hr></hr>
        <MyNavLink to="/about">About</MyNavLink>
        <MyNavLink to="/home">Home</MyNavLink>
        <hr></hr>
        {/* 注册路由 */}
        <Switch>
          <Route path="/about" component={About} />
          <Route path="/home" component={Home} />
          <Route path="/home" component={Test} />
        </Switch>
      </div>
    )
  }
}

我们看一下效果哈!

在这里插入图片描述
哎呀妈呀!完事儿!这就是 Switch 标签的使用,就这样!

总结:

  • 通常情况下,path 和 component 是一一对应的关系。
  • Switch 可以提高路由的匹配效率(单一匹配)。

【本部分相关代码资料】:我是𝒆𝒅. 的 gitee

解决多级路径刷新可能存在样式丢失问题

  • public/index.html 文件中引入样式时候,不要写 ./ 改写 / (常用)
  • public/index.html 文件中引入样式时候,不要写 ./ 改写 %PUBLIC_URL% (常用)
  • 使用 HashRouter

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

这部分说一下自严格匹配模糊匹配。一个一个来,什么是模糊匹配呢,我们之前的案例就是使用的模糊匹配,比如说我们给 Home 路由组件的匹配路由修改一下,改成 /home/a/b

    return (
      <div>
        <Header name="我是ed." />
        <hr></hr>
        <MyNavLink to="/about">About</MyNavLink>
        <MyNavLink to="/home">Home</MyNavLink>
        <hr></hr>
        {/* 注册路由 */}
        <Switch>
          <Route path="/about" component={About} />
          <Route path="/home/a/b" component={Home} />
        </Switch>
      </div>
    )

你说我现在点击 home 菜单,还能匹配到 home 组件吗?

在这里插入图片描述
试了一下,找不到了,当然 About 组件肯定是没有问题的啦。为啥找不到 Home 了?因为菜单点击之后,菜单他给的路由是 /home,但是呢,要想渲染出 Home 组件,需要匹配的路由是 /home/a/b 吧?这时候,路由组件 Home 一看:卧槽,老子要的是 /home/a/b 这么长,你TMD的给个 /home 就想把老子唤出来,做梦!呸!! 所以 Home 组件他不出来。

但是,我们如果换一下呢?

    return (
      <div>
        <Header name="我是ed." />
        <hr></hr>
        <MyNavLink to="/about">About</MyNavLink>
        <MyNavLink to="/home/a/b">Home</MyNavLink>
        <hr></hr>
        {/* 注册路由 */}
        <Switch>
          <Route path="/about" component={About} />
          <Route path="/home" component={Home} />
        </Switch>
      </div>
    )
  }

上面代码,点击 home 菜单,给的路由是 /home/a/b ,这时候 Home 组件会不会出来呢?

在这里插入图片描述
诶,Home 组件出来了!为啥呢? 当点击 Home 菜单,他给的是 /home/a/b 路由,然后路由组件 Home 一看:卧槽!这么长,而且我要的是 /home, 尽管不全一样,但是第一个这不是匹配上了嘛!他给的太多了,我无法拒绝啊!要不我去看看吧!所以 Home 组件出来了。

但是再看下面这样一种情况,菜单给 /a/home/b,Home 要 /home ,他会不会出来?

render() {
    return (
      <div>
        <Header name="我是ed." />
        <hr></hr>
        <MyNavLink to="/about">About</MyNavLink>
        {/* <MyNavLink to="/home">Home</MyNavLink> */}
        <MyNavLink to="/a/home/b">Home</MyNavLink>
        <hr></hr>
        {/* 注册路由 */}
        <Switch>
          <Route path="/about" component={About} />
          <Route path="/home" component={Home} />
          {/* <Route path="/home/a/b" component={Home} /> */}
        </Switch>
      </div>
    )
  }

这时候,Home 组件出不出呢?
在这里插入图片描述
他不出来,为啥呢? 菜单给 /a/home/b,Home 要 /home ,Home 组件一看,卧槽,尽管有我想要的东西,但是他第一个是/a,那肯定不是找我,那我不出去! 所以 Home 不出来。

OK,上面就简单的说了一下路由的模糊匹配

那我们不想让他匹配,我给 /a/home/b 必须要路由匹配是 /a/home/b 全部匹配正确的组件出来,其他的不许出来怎么办?

其实很简单,我们只需要给 <Route/> 标签添加一个属性 exact 设置为 true 就可以了!加上之后,就是严格匹配

<Route exact={true} path="/home" component={Home} />

我们给标签加上了 exact={true} 就是启用了严格匹配,那么,必须路由给的是 /home Home 组件才会出来,如果给 /a/home/b 也不会出来!

当然,我们可以简写,默认为 true

<Route exact path="/home" component={Home} />

效果是一样的

在这里插入图片描述

好了,关于路由的 模糊匹配 和 严格匹配 就是这些内容。这部分先到这里。

总结:

  • 默认使用的是模糊匹配。输入的路径必须包含匹配的路径,并且顺序一定要一致。
  • 开启严格模式匹配:<Route exact={true} path="/home" component={Home} />
  • 严格匹配不要随意开启,需要的时候在开也不迟,有些时候开启会导致无法继续匹配二级路由。

【本部分相关代码资料】:我是𝒆𝒅. 的 gitee

Redirect 重定向

我们还是按照上面的案例来,我们取消严格匹配和模糊匹配编写的代码哈,就回到最开始点击 home 菜单去 /home,点击 about 菜单去 /about。

在这里插入图片描述
我们看上面的动态图,我们在最开始进入这个页面,也就是地址是 localhost:3000 的时候,这个页面内容部分什么也没有的,为啥?因为localhost:3000相当于 localhost:3000/ 吧? router 拿着地址的 / 去匹配没有匹配到相对应的组件去渲染,所以导致最开始没有东西渲染吧。

我不想这样,我想已经入页面,也就是没有匹配到东西的时候,给我打开默认的页面 /home,这个应该怎么实现?

其实这个问题就需要使用重定向来解决,当然 router 也给我们提供了一个标签帮我们实现重定向 —— Redirect 作用就是重定向。

首先我们引入这个 Redirect。然后一般是在路由注册的最下面使用。

// 创建外壳组件
import React, { Component } from "react";
import { Route, Switch, Redirect } from 'react-router-dom'
import MyNavLink from "./components/MyNavLink";
import Header from "./components/Header";
import Home from "./pages/Home";
import About from "./pages/About";

// 创建并暴露APP组件
export default class App extends Component {

  render() {
    return (
      <div>
        <Header name="我是ed." />
        <hr></hr>
        <MyNavLink to="/about">About</MyNavLink>
        <MyNavLink to="/home">Home</MyNavLink>
        <hr></hr>
        {/* 注册路由 */}
        <Switch>
          <Route path="/about" component={About} />
          <Route path="/home" component={Home} />
          <Redirect to="/home" />
        </Switch>
      </div>
    )
  }
}

OK,上面代码我们引入了 Redirect ,然后在组件注册的最下面加了 <Redirect to="/home" /> 这句话,这句话是什么意思呢?就是当没有组件被匹配到的时候,会自动跳转到 /home,我们保存看一下效果。

在这里插入图片描述
当我们输入 localhost:3000 直接敲击回车之后,他会自动前往 /home,而不是显示空白页面。

总结:Redirect 标签一般写在所有路由注册的最下面,当所有路由都无法匹配时,跳转到 Redirect 指定的路由。

【本部分相关代码资料】:我是𝒆𝒅. 的 gitee

嵌套路由(二级路由)

这一部分,我们主要说一下嵌套路由,也就是常说的二级路由,啥意思呢,我们看一个案例就可以了。看下面的截图,我们在之前案例基础上(注意一个问题,为了讲解,我们把上部分案例 App 组件中,路由重定向的路径由 /home 改为 /about )给 Home 的组件修改一下,在 Home 组件中继续创建两个菜单 News 和 Message,点击 New 菜单在 Home 组件下展示 News 组件,点击 Message 菜单在 Home 组件下展示 Message 组件。

在这里插入图片描述
只要是从本篇博文开头看,并且能看懂每部分的知识点的话,上面的截图内容就很好理解,可以看出来哈,about 菜单切出来的 About 组件和以前一样,就改造了 Home 组件,点击 Home 菜单切出 Home 组件之后呢,Home 组件除了最开始案例输出的那段话之外,下面又有一个导航菜单,分别是 News 和 Message,然后呢,点击News菜单,在 Home 组件中再切换出 News 组件,点击 Message 菜单,在 Home 组件中再切换出 Message 组件。

我们第一步哈,先把 News 组件 和 Message 组件写出来,然后在 Home 组件中引入出来。我们分析,既然 News 组件 和 Message 组件是通过路由切换控制显隐的,所以说他们也是路由组件吧?写在 pages 文件夹下。又因为他们是在 Home 组件下面包含的,所以说我们把这两个组件,创建在 Home 组件当中。

在这里插入图片描述
首先是 News 组件:

import React, { Component } from 'react'

export default class News extends Component {
  render() {
    return (
      <ul>
        <li>我是𝒆𝒅. 成为世界上最帅的人</li>
        <li>我是𝒆𝒅. 成为世界上最牛B的前端开发</li>
        <li>我是𝒆𝒅. 成为世界上第一个牛B死的人</li>
      </ul>
    )
  }
}

其次是 Message 组件:

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="/message3">message003</a>
        </li>
      </ul>
    )
  }
}

然后编写完两个组件的内容,接下来我们把两个组件用 router 在 Home 组件中引入一下。

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

export default class Home extends Component {
  render() {
    return (
      <div>
        <h3>你好ed. 我是 Home 组件</h3>

        <hr />

        <MyNavLink to="/news">News</MyNavLink>
        <MyNavLink to="/message">Message</MyNavLink>
        
        <hr />

        <Switch>
          <Route path="/news" component={News} />
          <Route path="/message" component={Message} />
        </Switch>
      </div>
    )
  }
}

这里的代码仔细看哈,注意点击两个菜单,切换的路径,以及路由匹配的路径。是完全按照之前 Home 组件 和 About 组件一样编写的。点击 News 菜单 前往 /news 路径,渲染 News 组件;点击 Message 菜单 前往 /message 路径,渲染 Message 组件。

理论说,这样子就可以了吧?我们保存刷新一下。

在这里插入图片描述
我们看动图,我们点击 About 菜单,切换到 About 组件,这个是完全没有问题的,和以前一模一样。当我们点击 Home 菜单的时候,页切换到了 Home 组件,但是!当我们点击 Home 组件中的 News 菜单,应该切换到 /news 路径,展示 News 组件的时候,他却跳回了 /about 路径,点击 Message 菜单效果也一样。不对啊!没啥没展示 News 组件和 Message 组件?

OK,原因是:先注册的组件,先匹配。

我们刷新完页面,App 组件中的

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

这些组件先注册的没问题吧?所以说我们点击 About 菜单,切换到 About 组件;点击 Home 菜单的时候,页切换到了 Home 组件这一步是没有问题的。关键来咯!当我们切换到 Home 组件显示的时候,这两个组件

<Route path="/news" component={News} />
<Route path="/message" component={Message} />

才被注册!

那 当我们点击 News 菜单,地址去了 /news,路由器要重新匹配路由啊!先注册的组件,先匹配。
所以说,先匹配

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

这三个吧?是不是先匹配这三个?是的!你看,这三个那个能匹配到 /news 路径?第一个第二个都不行,只有第三个匹配到了,匹配到了重定向到 /about,所以说,直接跳转到了 About 组件,就是动图的效果,Message 也一样。

OK,怎么解决这个问题?很简单,我们在 Home 中以二级菜单的形式引入的 News 和 Message,所以我们给 News 和 Message 设置路由的时候,需要把他的父路由地址添加上,也就是把 /home 添加上。顺便把二级菜单的重定向也加上吧。

  render() {
    return (
      <div>
        <h3>你好ed. 我是 Home 组件</h3>

        <hr />

        <MyNavLink to="/home/news">News</MyNavLink>
        <MyNavLink to="/home/message">Message</MyNavLink>
        
        <hr />

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

OK,代码这样写,二级菜单就可以使用了。

在这里插入图片描述
这时候我们在来分析一下路由匹配的过程。前面都一样,就当我们点击 News 菜单,地址去了 /home/news,路由器要重新匹配路由啊!

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

这三个哪个能够匹配到?第二个吧?模糊匹配啊!所以能找到 Home 组件,展示 Home 组件,然后在能和谁匹配到?

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

第一个吧?因为路径就是 /home/news ,完全一样,直接展示 News 组件。同理,Message 也一样。

OK,这就是二级嵌套的使用方式和注意事项。

总结:

  1. 注册子路由时,要写上父路由的 path 值。
  2. 路由的匹配时按照注册路由的顺序执行的。

好了,这部分暂时告一段落,后边继续研究其他内容。

【本部分相关代码资料】:我是𝒆𝒅. 的 gitee

向路由组件传递 params 参数

接下来我们这部分说一下如何向 向路由组件传递 params 参数 。我们还是以案例为主,把功能实现,就会了哈。

首先,我们在上一部分的案例代码上继续添加功能。

在这里插入图片描述
上一个案例,我们在点击 Message 这个二级菜单之后,显示出来 Message 路由组件,OK,我们接下来要做的事情稍微复杂一点点,但是只要理清结构,则很简单哈。

首先,我们在 Message 组件下面,再添加一级菜单,作为整个项目的三级菜单,有三个,分别是消息001消息002消息003 菜单。我们在创建一个 Detail 组件,放进 Message 组件中作为展示,点击Message 组件上不同的消息菜单,我们传递不同的数据,展示在新创建的 Detail 组件组件中。效果大体就是下面的动图效果。

在这里插入图片描述
OK,我们先把 Detail 组件创建出来。首先分析哈,这个组件是不是在 Message 组件中使用的?是吧?所以说,我们 Detail 组件文件放在 Message 下面就可以。

在这里插入图片描述

我们编写 Detail 组件的内容:

import React, { Component } from 'react'

export default class Detail extends Component {
  render() {
    return (
      <div>
        <ul>
          <li>编号:XXX</li>
          <li>标题:XXX</li>
          <li>内容:XXX</li>
        </ul>
      </div>
    )
  }
}

然后我们修改 Message 组件内容,把之前的 a 标签改为 Link ,因为现在不需要高亮,不用 NavLink 了就,然后在去注册路由组件。我们这一次哈,菜单不写死了,我们使用 状态 维护一下,然后遍历出来可以吧。

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

export default class Message extends Component {
  state = {
    messageList: [{
      id: "1",
      title: '消息001'
    }, {
      id: "2",
      title: '消息002'
    }, {
      id: "3",
      title: '消息003'
    }]
  }
  render() {
    let { messageList } = this.state
    return (
      <div>
        <ul>
          {
            messageList.map(messageObj => {
              return (
                <li key={messageObj.id}>
                  <Link to="/home/message/detail">{messageObj.title}</Link>
                </li>
              )
            })
          }
        </ul>

        <hr />

        <Route path="/home/message/detail" component={Detail} />

      </div>
    )
  }
}

编写的方式和之前一样,注意,这里的 Detail 是三级菜单,所以说把他的父路径们全部加上,OK,我们保存代码刷新一下看一下页面,注意路径变化。

在这里插入图片描述
OK,效果是有的吧?点击三个消息都是去展示 Detail 页面吧。

这是后,我们需要怎么做呢,我们点击消息菜单的时候,我们把菜单的 id 和 title 传递给路由组件 Detail 中,然后应该在 Detail 路由组件中获取到传递进来的 id 和 title。OK,怎么传递呢,接下来就是这部分的重点 —— 向路由组件传递 params 参数

怎么传传递呢?

<Link to={`/home/message/detail/${messageObj.id}/${messageObj.title}`}>{messageObj.title}</Link>

直接把参数拼在路径后面。这样页面首先是可以渲染的!因为模糊匹配,可以显示出 Detail 路由组件,这没啥好有疑虑的。关键是仅仅修改 Link 标签,Detail 组件中可以获得最后传进来的两个参数吗?

比如我们点击 消息001 路径就变成了 /home/message/detail/1/消息001。组件能匹配到,但是路径最后的 /1/消息001 就没了,为啥呢,因为渲染 Detail 组件只需要 /home/message/detail,只要匹配到了,后边我就不管了,他爱是啥是啥,我不听不看不管。所以 Detail 组件中获取不到我们通过 params 传递进来的参数。怎么就获取到了呢?在注册路由的时候,告诉路由,我这里需要两个参数就可以,怎么告诉呢:

<Route path="/home/message/detail/:id/:title" component={Detail} />

使用 /:参数名 的方式,记住,你这里配置的参数顺序,需要和传递的参数顺序一致。这样他就知道,你 到 /home/message/detail 路径的时候需要渲染 Detail 组件,并且传进来两个参数,分别是 id、title

好了,数据传递了,我怎么在 Detail 中接收呢?OK,首先明白一点!我们这是在往路由组件传递数据。传递数据,数据保存在 props 里面吧!所以我们直接去打印 Detail 组件接受到的 props

  render() {
    console.log(this.props)
    return (
      <div>
        <ul>
          <li>编号:XXX</li>
          <li>标题:XXX</li>
          <li>内容:XXX</li>
        </ul>
      </div>
    )
  }

我们看打印的数据是啥:

在这里插入图片描述
这个数据我们很熟是吧!在前面说过会用到,OK,现在用到了。

在这里插入图片描述
看到没,props 中的 match 中的 params 就是我们在路由组件中传递的数据。

参数名和我们接受设置的参数名一致。

<Route path="/home/message/detail/:id/:title" component={Detail} />

我们设置的第一个是 id,第二个是 title 。没问题,params 数据接收到了。

接下来要做的,就是展示了吧,这里哈,我们传递了消息的 id ,理论上应该请求后台,根据 id 获取这条数据的内容,但是我们这里没有后台,所以说呢,我们就写一个假的,然后 id 匹配起来,我们就展示一下就可以了,都是基本的知识,不细说了,直接上代码吧。

import React, { Component } from 'react'


const detailData = [
  { id: '1', content: '你好,王肖杰' },
  { id: '2', content: '你好,王肖洛' },
  { id: '3', content: '你好,王俊天' }
]

export default class Detail extends Component {
  render() {
    console.log(this.props)
    // 接收 params 参数
    const { id, title } = this.props.match.params
    const findResult = detailData.find(detailObj => {
      return detailObj.id === id
    })
    return (
      <div>
        <ul>
          <li>编号:{id}</li>
          <li>标题:{title}</li>
          <li>内容:{findResult.content}</li>
        </ul>
      </div>
    )
  }
}

OK,我们直接看结果吧!

在这里插入图片描述
OK啊,效果都出来了,很棒哈!

好了,关于 向路由组件传递 params 参数 的部分就到这里了,下面的部分继续其他方式的传参。加油宝子们!

params 方式向路由传递参数 总结:

  • 路由连接(携带参数):<Link to="/home/message/detail/ed/10">我是𝒆𝒅.</Link>
  • 注册路由(声明接收):<Route path="/home/message/detail/:name/age" component={Detail} />
  • 接收参数:const { name, age} = this.props.match.params

【本部分相关代码资料】:我是𝒆𝒅. 的 gitee

向路由传递 search 参数

OK哈,上面我们学习了一下 向路由组件传递 params 参数,这一部分我们换一种传参方式 —— search 传参

页面功能还是和上一部分的案例是一样的,只是传参的方式变了。

首先说,通过 search 传递参数 比 通过 params 传递参数简单,但是接收参数的时候稍微复杂一点点。

我们先看 Link 部分。

之前使用 params 传递参数是这样写的:

<Link to={`/home/message/detail/${messageObj.id}/${messageObj.title}`}>{messageObj.title}</Link>

但是使用 search 传参,不是啦,search 传参就和 get 请求传递参数类似,使用 ? 和 & 在地址进行拼接,就是 query 传参,啊哈哈哈哈,但是人家叫 search 传参。怎么改,就是下面的样子:

<Link to={`/home/message/detail?id=${messageObj.id}&title=${messageObj.title}`}>{messageObj.title}</Link>

看到了吧,是不是和一个 get 请求一样。对,就这么简单又好理解。

然后注册路由需要怎么声明接收的 search 参数呢?

<Route path="/home/message/detail" component={Detail} />

search 参数 无需声明接收,正常注册路由即可。

完事了,传递参数结束了,接下来就是接受参数,这个部分稍微复杂一点,参数还是保存在 props 下面,所以照例打印一下 props 看一下传递进来的参数在哪里。

在这里插入图片描述
看到没,绿色框标注起来的就是接受到的参数,是的,一个字符串,但是我们传递进来的参数都有吧?OK,接下来解析就可以了。

【注意】这个解析,是一个经常被问到的面试题!!!!怎么解析url中传递的参数??

现在传递进来的是字符串 ?id=1&title=消息001,这种编码格式其实叫做 urlencoded
我们想要的是对象

{
	id: "1",
	title: "消息001"
}

当然,使用原始的 js 代码自己一点一点解析,完全没有问题。bug 正确答案是使用 querystring。这个库脚手架已经帮我们实现了,不需要额外安装,他的作用就是:

id=1&title=消息001

{
	id: "1",
	title: "消息001"
}

的相互转换。

首先引入吧!

import qs from 'qs'

然后有两个方法记一下 stringifyparse。第一个是对象转字符串,第二个是字符串转对象。

OK,那我们解析完展示一下就完事了。

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

const detailData = [
  { id: '1', content: '你好,王肖杰' },
  { id: '2', content: '你好,王肖洛' },
  { id: '3', content: '你好,王俊天' }
]

export default class Detail extends Component {
  render() {
    console.log(this.props)
    // 接收 search 参数
    const {search} = this.props.location
    const {id, title} = qs.parse(search.slice(1))

    const findResult = detailData.find(detailObj => {
      return detailObj.id === id
    })
    return (
      <div>
        <ul>
          <li>编号:{id}</li>
          <li>标题:{title}</li>
          <li>内容:{findResult.content}</li>
        </ul>
      </div>
    )
  }
}

好的,保存看效果。

在这里插入图片描述
好了,这部分关于向路由传递 search 参数的问题就结束了。

向路由传递 search 参数 总结:

  • 路由连接(携带参数):<Link to="/home/message/detai?name=ed&age=10">我是𝒆𝒅.</Link>
  • 注册路由(无需声明,正常注册):<Route path="/home/message/detail" component={Detail} />
  • 接收参数:const { search} = this.props.location 。注意获取到的search是urlencoded编码格式,需要借助querystring解析。

【本部分相关代码资料】:我是𝒆𝒅. 的 gitee

向路由传递 state 参数

前面说了 params 传参和 search 传参,现在说一下另一种传参方式 —— state 传参。

注意哈,这里的 state 和我们 react 学的 状态的 state 不是一个东西哈,两回事。

其他都一样,就传参收参的编写方式不同,我们重点看一下,使用 state 传递参数需要怎么修改呢!

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

通过 state 方式传参,to 属性接收的是一个 对象,里面有一个 pathname 是跳转的路径,state 就是传递的参数,也是对象的形式。

然后是注册路由部分,这里呢,和 search 一样,无需声明,正常注册。

<Route path="/home/message/detail" component={Detail} />

最后接收参数的时候,照例,打印一下 props 看看:

在这里插入图片描述
OK,我们可以获取到 state 传递的参数吧,正好也是对象,毕竟我们传进来得 state 就是个对象嘛。

然后正常解析渲染就可以啦呀!

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


const detailData = [
  { id: '1', content: '你好,王肖杰' },
  { id: '2', content: '你好,王肖洛' },
  { id: '3', content: '你好,王俊天' }
]

export default class Detail extends Component {
  render() {
    console.log(this.props)

    // 接收 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 findResult = detailData.find(detailObj => {
      return detailObj.id === id
    }) || {}
    return (
      <div>
        <ul>
          <li>编号:{id}</li>
          <li>标题:{title}</li>
          <li>内容:{findResult.content}</li>
        </ul>
      </div>
    )
  }
}

好的,保存代码,刷新查看一下效果!

在这里插入图片描述
好的效果没问题。有一点需要注意哈,这个 state 传递参数啊,尽管在浏览器地址栏没有显示传递的参数,但是刷新也可以保留住参数,不过在你浏览器清理掉缓存之后呢,数据就丢失了哈。注意一下。

向路由传递 state参数 总结:

  • 路由连接(携带参数):<Link to={{pathname:'/home/message/detail', state:{name:'ed', age:10}}}>我是𝒆𝒅.</Link>
  • 注册路由(无需声明,正常注册):<Route path="/home/message/detail" component={Detail} />
  • 接收参数:const {id, title} = this.props.location.state ,刷新也可以保留住参数。

【本部分相关代码资料】:我是𝒆𝒅. 的 gitee

中途总结 - 向路由组件传参

我们一共讲了三种向路由组件传参的方式:

  • params:使用频率的最多
  • search:使用频率次之
  • state:相对较少

路由的工作原理

路由的工作原理其实就是两个操作。

  • push:就是压栈操作,一个压一个,会留下痕迹。
  • replace:会替换掉以前的,不会留下痕迹。

默认路由的跳转方式就是 push

啥叫留下痕迹啊,就是我们在浏览器点击不同的链接,去往新的地址,浏览器都是会有记录的,也就是说,我们可以通过返回,前往我们上一个页面。

在这里插入图片描述

只要留有痕迹,就可以返回上一个页面,只要不留痕迹,就没得可返回的。我们到现在使用的路由都是默认用的 push ,所以说你的每一次操作都是有记录的。但是如果用 replace 就是没有记录的,不留痕迹。那怎么给路由开启 replace 模式呢?很简单。

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

给需要启用 replace 模式的 Link 标签添加 replace 属性就可以了。

编程式路由导航

借助this.prosp.history对象上的API对操作路由跳转、前进、后退

  • this.prosp.history.push()
  • this.prosp.history.replace()
  • this.prosp.history.goBack()
  • this.prosp.history.goForward()
  • this.prosp.history.go()
    // 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})
    // 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})

【本部分相关代码资料】:我是𝒆𝒅. 的 gitee

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 (
            <h1>react router 学习</h1>
        )
    }
}
export default withRouter(Header)

这样子的话,一般组件 Header 就拥有了 路由组件 的一些方法,就可以使用回退,前进等操作路由的功能。

BrowserRouter与HashRouter的区别

底层原理不一样:

  • BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。
  • HashRouter使用的是URL的哈希值。

path表现形式不一样

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

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

  • (1).BrowserRouter没有任何影响,因为state保存在history对象中。
  • (2).HashRouter刷新后会导致路由state参数的丢失!!!

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

好了,与路由相关的东西全部结束了。很多,慢慢消化,拜拜!!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值