React全家桶:SPA--路由--路由组件和一般组件--NavLink组件--Switch--匹配模式--重定向--withRouter

148 阅读10分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情

写在前面

在最近看了React之后,一直觉得学的懵懵然,虽然很多大佬的手写笔记,写的都很不错,但是我一直没有我想要的那种细无巨细,比如类式组件this指向问题的追根溯源,又比如三大实例属性简写的由来,总之我还是决定做一份事无巨细的笔记。

那就让我们开始吧!

对SPA应用的理解

1. 单页Web应用(single page web application,SPA)。

2. 整个应用只有一个完整的页面

3. 点击页面中的链接不会刷新页面,只会做页面的局部更新。

4. 数据都需要通过ajax请求获取, 并在前端异步展现。

路由的理解

1. 什么是路由?

1. 一个路由就是一个映射关系(key:value)

2. key为路径, value可能是function或component

路由分类

1. 后端路由:

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

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

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

2. 前端路由:

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

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

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

前端路由原理

BOM对象上的history属性

image.png

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

image.png

  • 创建history对象

方法一:会刷新页面

// let history = History.createBrowserHistory() //方法一,直接使用H5推出的history身上的API

方法二:并不会刷新页面

let history = History.createHashHistory() //方法二,hash值(锚点)
  • 浏览器的历史记录是一个栈的数据结构

路由的基本使用

react-router-dom的理解

1. react的一个插件库。

2. 专门用来实现一个SPA应用。

3. 基于react的项目基本都会用到此库。

  • react-router库有三种实现 有web,native,any3个平台。

  • 路由叫做Route、路由器叫做Router

  • 一般React-xxx就是一个React插件库

官方网站

  • 印记中文

传送门

docschina.org/

  • React-route-dom

传送门

react-router.docschina.org/

安装React-router-dom

下载react-router-dom: npm install --save react-router-dom

yarn add react-router-dom

react-router demo1 (3).gif

  • 通过点击导航区的导航,引起历史记录的路径改变,被前端路由器检测到。

页面更新

原生html中,靠<a>跳转不同的页面

{/* 原生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
import {Link,Route} from 'react-router-dom'
{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
<Link className="list-group-item" to="/about">About</Link>
<Link className="list-group-item" to="/home">Home</Link>
  • 注意to属性的值没有“.”。

选择确切的Router

image.png

  1. 选择BrowserRouter

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

image.png

2.注册路由

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

注意:需要将注册路由和路由链接写在一个路由器内

image.png

  • 在Index入口文件,书写路由器,为了组件更新书写的方便
//引入ReactDOM
import ReactDOM from 'react-dom'

ReactDOM.render(
	<BrowserRouter>
		<App/>
	</BrowserRouter>,
	document.getElementById('root')
)

路由组件和一般组件

使用hashRouter

//
import {HashRouter} from 'react-router-dom'

ReactDOM.render(
	<HashRouter>
		<App/>
	</HashRouter>,
	document.getElementById('root')
)

路由组件

  • 创建pages文件夹,内部放入路由组件的文件夹

  • 路由组件带有属性

image.png

1.写法不同:

一般组件:`<Demo/>`
路由组件:`<Route path="/demo" component={Demo}/>`
    

2.存放位置不同:

一般组件:components
路由组件:pages

3.接收到的props不同:

一般组件:写组件标签时传递了什么,就能收到什么
路由组件:接收到三个固定的属性

history:

    go: ƒ go(n)
    goBack: ƒ goBack()
    goForward: ƒ goForward()
    push: ƒ push(path, state)
    replace: ƒ replace(path, state)
    

location:

    pathname: "/about"
    search: ""
    state: undefined
    

match:

    params: {}
    path: "/about"
    url: "/about"

NavLink的使用

动态追加属性名

NavLink被点击的时候会给标签添加一个active属性。Bootstrap库中avtive正好就是这个背景色加深。可以使用动态activeClassName。

1. NavLink可以实现路由链接的高亮,通过activeClassName指定样式名。点击哪个标签就添加这个属性名。

{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
<NavLink activeClassName="atguigu" className="list-group-item" to="/about">About</NavLink>
<NavLink activeClassName="atguigu" className="list-group-item" to="/home">Home</NavLink>
  1. index.html文件书写样式
<style>
        .atguigu{
                background-color: rgb(209, 137, 4) !important;
                color: white !important;
        }
</style>

注意:这里引入的bootstrap库,所以记得添加权限

封装NavLink组件

目的

  • 解决代码冗余度

创建一个MyNavLink组件封装路由组件

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

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

标签内容是一个特殊的标签属性,叫做children。可以通过this.props.children可以获取标签体内容。

Switch的使用

报错

报错提示:'Switch' is not exported from 'react-router-dom'.

原因分析:因为最新版本的react-router-dom@v6版本已经将Switch替换成了Routes了,所以报错了。

解决方法

1.使用Routes替换Switch

2.卸载最新版本安装@v5版本就可以继续使用Switch了

Switch的使用

1.通常情况下,path和component是一一对应的关系。

2.Switch可以提高路由匹配效率(单一匹配)。

3.从上到下依次匹配

解决样式丢失问题

image.png

脚手架内置服务器

http://localhost:3000/ 就是通过webpack devServer开启的服务器

  • 解决多级路径刷新页面样式丢失的问题

Public文件夹就是localhost3000下面根路径,index.html就是访问路径的兜底页面。(路径出错)

解决办法:

1.public/index.html 中 引入样式时不写 ./ (代表在当前路径的文件夹下寻找)写 / (在http://localhost:3000/下直接寻找)

2.public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)绝对路径

3.使用HashRouter(少见)

路由路径为多级

请求bootstrap路径的变化

  • 在注册路由中添加了一级

在第一次启动项目,默认路径在http://localhost:3000/

也就是请求bootstrap的路径是http://localhost:3000/css/bootstrap.css

  • 刷新页面

刷新页面之后就会更换请求路径http://localhost:3000/atguigu/css/bootstrap.css

这样就会导致请求样式失败。由于路径层级错误,那么就会返回index.html

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

不用混用yarn 和 npm

  • 避免包的丢失

1.默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致,多给可以,少给不行,顺序不能改变)

2.开启严格匹配:<Route exact={true} path="/about" component={About}/>(exact可以简写)

简写<Route exact path="/about" component={About}/>

3严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由

Redirect(重定向)的使用

1.一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由

2.具体编码:

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

嵌套路由

react-router demo2.gif

嵌套路由

1.注册子路由时要写上父路由的path值(保证之前注册的路由也可以匹配上,保证父组件不会丢)

2.路由的匹配是按照所有注册路由的顺序进行的

3.组件一挂载,组件内部注册路由就会开始匹配,结合使用Redirect可以达到默认选择子组件的效果。

具体实现

  1. 编写路由链接(App组件)

{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink>
  1. 注册路由(App组件)

{/* 注册路由 */}
<Switch>
        <Route path="/about" component={About}/>
        <Route path="/home" component={Home}/>
        <Redirect to="/about"/>
</Switch>
  1. 注册路由(Home组件)
{/* 注册路由 */}
<Switch>
        <Route path="/home/news" component={News}/>
        <Route path="/home/message" component={Message}/>
        <Redirect to="/home/news"/>
</Switch>

向路由组件传递参数数据

react-router demo3 (2).gif

在message组件遍历状态

// 状态
state = {
        messageArr:[
                {id:'01',title:'消息1'},
                {id:'02',title:'消息2'},
                {id:'03',title:'消息3'},
        ]
}
messageArr.map((msgObj)=>{
        return (
                <li key={msgObj.id}>
                        {/* 向路由组件传递params参数 */}
                        <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
                </li>
        )
})

Detail组件

  1. 编写路由链接
 <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
  1. 注册路由(在Message组件)
{/* 声明接收params参数 */}
<Route path="/home/message/detail/:id/:title" component={Detail}/>
  • 实现效果

image.png

想Detail组件传递参数(使用parmas)

  • Link标签操作传递属性

注意:使用js表达式记得添加{}

 <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
  • 注册路径(接受参数)

注意:接受参数书写:,:后面的就是属性名

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

这里传递的属性会保存在路由组件的props属性内。

image.png

根据传递的数据进行渲染

  • 初始化状态
const DetailData = [
	{id:'01',content:'你好,中国'},
	{id:'02',content:'你好,尚硅谷'},
	{id:'03',content:'你好,未来的自己'}
]
  • 根据传入id使用数组的find方法
const findResult = DetailData.find((detailObj)=>{
        return detailObj.id === id
})

params参数

    路由链接(携带参数):<Link to='/demo/test/tom/18'}>详情`</Link>`
    
    注册路由(声明接收):`<Route path="/demo/test/:name/:age" component={Test}/>`
    
    接收参数:this.props.match.params
    

向路由组件传递search参数

  • Link标签操作传递属性

注意:使用js表达式记得添加{},书写带&的字符串

{/* 向路由组件传递search参数 */}
<Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>
  • search参数无需声明接收,正常注册路由即可

  • 查询传递的search属性是字符串形式

image.png

qs库使用

image.png

接收search参数

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

向路由组件传递state参数

state参数(不是组件中的那个state)

  • 向路由组件传递state参数(书写对象形式)
{/* 向路由组件传递state参数 */}
<Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>
  • state参数无需声明接收,正常注册路由即可
{/* state参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail}/>
  • Detail组件接受参数
// 接收state参数
const {id,title} = this.props.location.state || {}
  • 地址栏不带参数,刷新不改变

刷新仍然可以保住数据,因为BrowserRouter会维护history对象,state在location上,location在history身上。

  • 为了避免清除历史记录之后,请求失败

image.png

给state一个备用值“空对象”

路由跳转的两种模式 push与repalce

实现效果

react-router demo4.gif

历史记录回退

0 在回退的时候,都是根据栈保存的地址进行回退

push

  • 是一种压栈操作

image.png

replace

  • 是一种替换操作

在Link中添加replace属性。

Replace = {true} 简写为replace

image.png

编程式路由导航

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

-this.prosp.history.push()

-this.prosp.history.replace()

// 对应路由导航那个组件。Message组件

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})    
}
// 使用onClcik事件函数进行调用,这是使用的是非柯里化函数

    &nbsp;<button onClick={()=> this.pushShow(msgObj.id,msgObj.title)}>push查看</button>

    &nbsp;<button onClick={()=> this.replaceShow(msgObj.id,msgObj.title)}>replace查看</button>
    

goBack()、goForward()

  • this.prosp.history.goBack()

  • this.prosp.history.goForward()

  • this.prosp.history.go() :是前面2个API的合集,传递一个参数就是前进或者倒退的步数。

  • 函数书写

<button onClick={this.back}>回退</button>&nbsp;

<button onClick={this.forward}>前进</button>&nbsp;

<button onClick={this.go}>go</button>

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

go = ()=>{
        this.props.history.go(-2)

    }

withRouter的使用(是一个函数)

加工组件 withRoute

  • 引入withRoute
import {withRouter} from 'react-router-dom'
  • 暴露组件
export default withRouter(Header)

// withRouter可以加工一般组件,让一般组件具备路由组件所特有的API
// withRouter的返回值是一个新组件

BrowserRouter与HashRouter的区别

BrowserRouter与HashRouter的区别

1.底层原理不一样:

    BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。
    HashRouter使用的是URL的哈希值。(不会给服务器发送请求,会形成历史记录)
    

2.path表现形式不一样

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

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

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

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