React-Router

发布于 2022-08-03  35 次阅读


SPA的理解

  1. 单页 Web 应用(single page web application,SPA)
  2. 整个应用只有一个完整的页面
  3. 点击页面中的链接不会刷新页面,只会做页面的局部更新。
  4. 数据都需要通过ajax请求获取, 并在前端异步展现。

路由的理解

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

前端路由原理

  1. 直接使用 H5 推出的 history 身上的 API
  2. hash 值(锚点) → 兼容性极佳

react-router-dom

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

react-router-dom相关API

内置组件

  1. <BrowserRouter>
  2. <HashRouter>
  3. <Route>
  4. <Redirect>
  5. <Link>
  6. <NavLink>
  7. <Switch>

其它

  1. history 对象
  2. match 对象
  3. withRouter 函数

基本使用 Link && Route

  1. 明确好界面中的导航区、展示区
  2. 导航区的 a 标签改为 Link 标签
    1. <Link to="/xxxxx">Demo</Link>
  3. 展示区写 Route 标签进行路径的匹配
    1. <Route path='/xxxx' component={Demo}/>
  4. <App> 的最外侧包裹了一个 <BrowserRouter><HashRouter>

准备工作

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

代码

index.js

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

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

App.jsx

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

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">

							{/* 原生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>
					</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>
		)
	}
}

路由组件 VS 一般组件

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

NavLink 导航链接

NavLink 可以实现路由链接的高亮,通过 activeClassName 指定样式名

默认 activeClassNameactive

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

Switch

  1. 通常情况下,pathcomponent 是一一对应的关系。
    1. 如果不使用 Switch 就会导致重复 path 的组件同时展示在页面上
  2. Switch 可以提高路由匹配效率(单一匹配)。
    1. 从上往下顺序匹配,匹配到了前面的,就不匹配后面的了
import React, { Component } from 'react'
import {Route,Switch} from 'react-router-dom'
import Home from './pages/Home' //Home是路由组件
import About from './pages/About' //About是路由组件
import Header from './components/Header' //Header是一般组件
import MyNavLink from './components/MyNavLink'
import Test from './pages/Test'

export default class App extends Component {
	render() {
		return (
			<div>
				<div className="row">
					<div className="col-xs-offset-2 col-xs-8">
						<Header/>
					</div>
				</div>
				<div className="row">
					<div className="col-xs-2 col-xs-offset-2">
						<div className="list-group">
							{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
							<MyNavLink to="/about">About</MyNavLink>
							<MyNavLink to="/home">Home</MyNavLink>
						</div>
					</div>
					<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>
				</div>
			</div>
		)
	}
}

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

  1. public/index.html 中 引入样式时不写 .// (常用)
  2. public/index.html 中 引入样式时不写 ./%PUBLIC_URL% (常用)
  3. 使用 HashRouter

public 代表服务器根路径

e.g. 本地 localhost:3000/demo ,此时 public 路径就是 localhost:3000

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

  1. 默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
    1. path: /home , to: /home 能够匹配上
    2. path: /home , to: /home/a/b 能够匹配上
    3. path: /home , to: /a/home/b 不能匹配上
    4. 匹配步骤是,用 / 做分隔,分别把 home、a、b拿出来跟 home 顺序匹配
  2. 开启严格匹配:<Route exact={true} path="/about" component={About}/>
    1. 或者简写 <Route exact path="..." />
  3. 严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由

Redirect的使用

  1. 一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
  2. 具体编码 <Switch> <Route path="/about" component={About}/> <Route path="/home" component={Home}/> <Redirect to="/about"/> </Switch>

嵌套路由

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

Home.jsx 组件

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>我是Home的内容</h3>
					<div>
						<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>
			)
	}
}

向路由组件传递参数

  1. params 参数
    1. 路由链接(携带参数):<Link to='/demo/test/tom/18'}>详情</Link>
    2. 注册路由(声明接收):<Route path="/demo/test/:name/:age" component={Test}/>
    3. 接收参数:this.props.match.params
  2. search 参数
    1. 路由链接(携带参数):<Link to='/demo/test?name=tom&age=18'}>详情</Link>
    2. 注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
    3. 接收参数:this.props.location.search
    4. 备注:获取到的search是urlencoded编码字符串,需要借助querystring解析
  3. state 参数
    1. 路由链接(携带参数):<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>
    2. 注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
    3. 接收参数:this.props.location.state
    4. 备注:刷新也可以保留住参数

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:'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}>
									{/* 向路由组件传递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}/>
			</div>
		)
	}
}

Message/Detail/index.jsx

import React, { Component } from 'react'

const DetailData = [
	{id:'01',content:'你好,中国'},
	{id:'02',content:'你好,尚硅谷'},
	{id:'03',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 (
			<ul>
				<li>ID:{id}</li>
				<li>TITLE:{title}</li>
				<li>CONTENT:{findResult.content}</li>
			</ul>
		)
	}
}

Search

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}>

									{/* 向路由组件传递params参数 */}
									{/* <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link> */}

									{/* 向路由组件传递search参数 */}
									<Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>

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

				{/* search参数无需声明接收,正常注册路由即可 */}
				<Route path="/home/message/detail" component={Detail}/>

			</div>
		)
	}
}

Message/Detail/index.jsx

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

const DetailData = [
	{id:'01',content:'你好,中国'},
	{id:'02',content:'你好,尚硅谷'},
	{id:'03',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))

		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>
		)
	}
}

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:'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}>

									{/* 向路由组件传递params参数 */}
									{/* <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link> */}

									{/* 向路由组件传递search参数 */}
									{/* <Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link> */}

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

								</li>
							)
						})
					}
				</ul>
				<hr/>
				{/* 声明接收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}/>

			</div>
		)
	}
}

Message/Detail/index.jsx

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

const DetailData = [
	{id:'01',content:'你好,中国'},
	{id:'02',content:'你好,尚硅谷'},
	{id:'03',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 (
			<ul>
				<li>ID:{id}</li>
				<li>TITLE:{title}</li>
				<li>CONTENT:{findResult.content}</li>
			</ul>
		)
	}
}

push 与 replace

  • 默认 push ,加 replace 为 替换
<Link replace ...>{msgObj.title}</Link>

withRouter 的使用

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

App.jsx

import React, { Component } from 'react'
import {Route,Switch,Redirect} from 'react-router-dom'
import Home from './pages/Home' //Home是路由组件
import About from './pages/About' //About是路由组件
import Header from './components/Header' //Header是一般组件
import MyNavLink from './components/MyNavLink'

export default class App extends Component {
	render() {
		return (
			<div>
				<div className="row">
					<div className="col-xs-offset-2 col-xs-8">
						<Header/>
					</div>
				</div>
				<div className="row">
					<div className="col-xs-2 col-xs-offset-2">
						<div className="list-group">
							<MyNavLink to="/about">About</MyNavLink>
							<MyNavLink to="/home">Home</MyNavLink>
						</div>
					</div>
					<div className="col-xs-6">
						<div className="panel">
							<div className="panel-body">
								{/* 注册路由 */}
								<Switch>
									<Route path="/about" component={About}/>
									<Route path="/home" component={Home}/>
									<Redirect to="/about"/>
								</Switch>
							</div>
						</div>
					</div>
				</div>
			</div>
		)
	}
}

components/Header/index.jsx

Header 组件没有被路由组件包裹,是一般组件,所以用 withRouter 包裹使其有路由相关的能力

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() {
		console.log('Header组件收到的props是',this.props);
		return (
			<div className="page-header">
				<h2>React Router Demo</h2>
				<button onClick={this.back}>回退</button>&nbsp;
				<button onClick={this.forward}>前进</button>&nbsp;
				<button onClick={this.go}>go</button>
			</div>
		)
	}
}

export default withRouter(Header)

编程式路由导航

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

-this.prosp.history.push()
-this.prosp.history.replace()
-this.prosp.history.goBack()
-this.prosp.history.goForward()
-this.prosp.history.go()

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'},
		]
	}

	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})
		
	}

	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((msgObj)=>{
							return (
								<li key={msgObj.id}>

									{/* 向路由组件传递params参数 */}
									{/* <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link> */}

									{/* 向路由组件传递search参数 */}
									{/* <Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link> */}

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

									&nbsp;<button onClick={()=> this.pushShow(msgObj.id,msgObj.title)}>push查看</button>
									&nbsp;<button onClick={()=> this.replaceShow(msgObj.id,msgObj.title)}>replace查看</button>
								</li>
							)
						})
					}
				</ul>
				<hr/>
				{/* 声明接收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}/>

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

			</div>
		)
	}
}

BrowserRouter 与 HashRouter 的区别

  1. 底层原理不一样
    1. BrowserRouter 使用的是 H5 的 history API,不兼容 IE9 及以下版本。
    2. HashRouter 使用的是 URL 的哈希值。
  2. path 表现形式不一样
    1. BrowserRouter 的路径中没有#,例如:localhost:3000/demo/test
    2. HashRouter 的路径包含#,例如:localhost:3000/#/demo/test
  3. 刷新后对路由state参数的影响
    1. (1).BrowserRouter 没有任何影响,因为 state 保存在 history 对象中。
    2. (2).HashRouter 刷新后会导致路由 state 参数的丢失!!!
  4. 备注:HashRouter 可以用于解决一些路径错误相关的问题。
最后更新于 2023-07-20