目录
3.2 HashRouter 和 BrowserRouter
一、单页应用程序 SPA 的介绍
SPA: Single Page Application
单页面应用程序,整个应用中只有一个页面(index.html)
MPA : Multiple Page Application
多页面应用程序,整个应用中有很多个页面(*.html)
优点:
-
加快页面响应速度,降低了对服务器的压力
-
传统的多页面应用程序,每次请求服务器返回的都是一整个完整的页面
-
单页面应用程序只有第一次会加载完整的页面,以后每次请求仅仅获取必要的数据
-
-
更好的用户体验,运行更加流畅
缺点:
不利于 SEO 搜索引擎优化
-
因为 爬虫 只爬取 HTML 页面中的文本内容,不会执行 JS 代码
-
可以通过 SSR(服务端渲染 Server Side Rendering)来解决 SEO 问题
-
解释:先在服务器端把内容渲染出来,然后,返回给浏览器的就是纯 HTML 内容了
-
-
页面静态化,比如,对于一个电商应用可以为每一个商品生产一个静态的HTML页面,静态 HTML 页面中是带有文字内容的,所以,有利于 SEO 的
二、初识路由(17.0.2版本)
2.1 路由的概述
现代的前端应用大多都是 SPA(单页应用程序),也就是只有一个 HTML 页面的应用程序。因为它的用户体验更好、对服务器的压力更小,所以更受欢迎
为了有效的使用单个页面来管理原来多页面的功能,前端路由应运而生
前端路由的功能:让用户从一个视图(页面)导航到另一个视图(页面)
前端路由是一套映射规则,在React中,是 URL路径 与 组件 的对应关系
使用 React 路由简单来说就是:配置路径和组件(配对)
2.2 React模拟实现单页(SPA)应用程序
Home.js
import React, { Component } from 'react'
export default class Home extends Component {
render() {
return (
<div>Home组件</div>
)
}
}
My.js
import React, { Component } from 'react'
export default class My extends Component {
render() {
return (
<div>我的音乐组件</div>
)
}
}
Friend.js
import React, { Component } from 'react'
export default class Friend extends Component {
render() {
return (
<div>我的朋友组件</div>
)
}
}
index.js
/**
* 1. 导入react和react-dom
* 2. 创建 react 元素
* 3. 把 react 元素渲染到页面
*/
import React, { PureComponent } from 'react';
import ReactDOM from 'react-dom'
import Home from './views/Home'
import My from './views/My'
import Friend from './views/Friend'
class App extends PureComponent {
state = {
currentHash: ''
}
// hashchange 事件,即锚点值(hash值)改变就会触发
componentDidMount() {
// 注册事件
window.addEventListener('hashchange', () => {
console.log('hash值变了', window.location.hash);
this.setState({
currentHash: window.location.hash.slice(1)
})
console.log(window.location.hash.slice(1));
})
}
render() {
const { currentHash } = this.state
return (
<div>
<h3>App组件</h3>
<ul style={{display: 'flex'}}>
<li style={{listStyle: 'none', margin: '10px'}}><a style={{textDecoration: 'none'}} href="#/home">首页</a></li>
<li style={{listStyle: 'none', margin: '10px'}}><a style={{textDecoration: 'none'}} href="#/my">我的音乐</a></li>
<li style={{listStyle: 'none', margin: '10px'}}><a style={{textDecoration: 'none'}} href="#/friend">朋友</a></li>
</ul>
{
currentHash === '/home' && <Home></Home>
}
{
currentHash === '/my' && <My></My>
}
{
currentHash === '/friend' && <Friend></Friend>
}
</div>
)
}
}
// 渲染组件
// 18.2.0
// ReactDom.createRoot(document.getElementById('root')).render(<App />)
// 17.0.2
ReactDOM.render(<App />, document.getElementById('root'))
三、路由的基本使用(17.0.2版本)
3.1 基本步骤
安装
- npm install react-router-dom@5.2.0 --save
react-router-dom
这个包提供了三个核心的组件
- import { HashRouter, Route, Link } from 'react-router-dom'
使用HashRouter
包裹整个应用,一个项目中只会有一个Router
<Router>
<div className="App">
// … 省略页面内容
</div>
</Router>
使用Link指定导航链接
<Link to="/first">页面一</Link>
<Link to="/two">页面二</Link>
使用Route
指定路由规则
// 在哪里写的Route,最终匹配到的组件就会渲染到这
<Route path="/first" component={First}></Route>
Home.js
import React, { Component } from 'react'
export default class Home extends Component {
render() {
return (
<div>Home组件</div>
)
}
}
My.js
import React, { Component } from 'react'
export default class My extends Component {
render() {
return (
<div>我的音乐组件</div>
)
}
}
Friend.js
import React, { Component } from 'react'
export default class Friend extends Component {
render() {
return (
<div>我的朋友组件</div>
)
}
}
index.js
/**
* 1. 导入react和react-dom
* 2. 创建 react 元素
* 3. 把 react 元素渲染到页面
*/
import React, { PureComponent } from 'react';
import ReactDOM from 'react-dom'
import { HashRouter, Route, Link } from 'react-router-dom'
import Home from './views/Home'
import My from './views/My'
import Friend from './views/Friend'
// Link:导航链接
// Route:指定路由规则
// HashRouter:整个路由组件,类似于 Vue 中的 VueRouter
class App extends PureComponent {
state = {
currentHash: ''
}
// hashchange 事件,即锚点值(hash值)改变就会触发
componentDidMount() {
// 注册事件
window.addEventListener('hashchange', () => {
console.log('hash值变了', window.location.hash);
this.setState({
currentHash: window.location.hash.slice(1)
})
console.log(window.location.hash.slice(1));
})
}
render() {
return (
<HashRouter>
<div>
<h3>App组件</h3>
<ul style={{display: 'flex'}}>
<li style={{listStyle: 'none', margin: '10px'}}>
<Link style={{textDecoration: 'none'}} to="/home">首页</Link>
</li>
<li style={{listStyle: 'none', margin: '10px'}}>
<Link style={{textDecoration: 'none'}} to="/my">我的音乐</Link>
</li>
<li style={{listStyle: 'none', margin: '10px'}}>
<Link style={{textDecoration: 'none'}} to="/friend">朋友</Link>
</li>
</ul>
<Route path="/home" component={Home}></Route>
<Route path="/my" component={My}></Route>
<Route path="/friend" component={Friend}></Route>
</div>
</HashRouter>
)
}
}
// 渲染组件
// 18.2.0
// ReactDom.createRoot(document.getElementById('root')).render(<App />)
// 17.0.2
ReactDOM.render(<App />, document.getElementById('root'))
3.2 HashRouter 和 BrowserRouter
Router 组件:包裹整个应用,一个 React 应用只需要使用一次
两种常用 Router:HashRouter
和 BrowserRouter
-
HashRouter:使用 URL 的哈希值实现(http://localhost:3000/#/first)
-
原理:监听 window 的
hashchange
事件来实现的
-
-
(推荐)BrowserRouter:使用 H5 的 history API 实现(http://localhost:3000/first)
-
原理:监听 window 的
popstate
事件来实现的
-
index.js
/**
* 1. 导入react和react-dom
* 2. 创建 react 元素
* 3. 把 react 元素渲染到页面
*/
import React, { PureComponent } from 'react';
import ReactDOM from 'react-dom'
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
import Home from './views/Home'
import My from './views/My'
import Friend from './views/Friend'
// Link:导航链接
// Route:指定路由规则
// HashRouter:整个路由组件,类似于 Vue 中的 VueRouter
class App extends PureComponent {
state = {
currentHash: ''
}
// hashchange 事件,即锚点值(hash值)改变就会触发
componentDidMount() {
// 注册事件
window.addEventListener('hashchange', () => {
console.log('hash值变了', window.location.hash);
this.setState({
currentHash: window.location.hash.slice(1)
})
console.log(window.location.hash.slice(1));
})
}
render() {
return (
<Router>
<div>
<h3>App组件</h3>
<ul style={{display: 'flex'}}>
<li style={{listStyle: 'none', margin: '10px'}}>
<Link style={{textDecoration: 'none'}} to="/home">首页</Link>
</li>
<li style={{listStyle: 'none', margin: '10px'}}>
<Link style={{textDecoration: 'none'}} to="/my">我的音乐</Link>
</li>
<li style={{listStyle: 'none', margin: '10px'}}>
<Link style={{textDecoration: 'none'}} to="/friend">朋友</Link>
</li>
</ul>
<Route path="/home" component={Home}></Route>
<Route path="/my" component={My}></Route>
<Route path="/friend" component={Friend}></Route>
</div>
</Router>
)
}
}
// 渲染组件
// 18.2.0
// ReactDom.createRoot(document.getElementById('root')).render(<App />)
// 17.0.2
ReactDOM.render(<App />, document.getElementById('root'))
3.3 路由的执行过程
-
点击 Link 组件(a标签),修改了浏览器地址栏中的 url
-
React 路由监听到地址栏 url 的变化 hashChange popState
-
React 路由内部遍历所有 Route 组件,使用路由规则(path)与 pathname(hash)进行匹配
-
当路由规则(path)能够匹配地址栏中的 pathname(hash) 时,就展示该 Route 组件的内容
3.4 Link与NavLink
Link
组件最终会渲染成a标签,用于指定路由导航
-
to属性,将来会渲染成a标签的href属性
-
Link
组件无法实现导航的高亮效果
NavLink
组件,一个更特殊的Link
组件,可以用用于指定当前导航高亮
-
to属性,用于指定地址,会渲染成a标签的href属性
-
activeClass: 用于指定高亮的类名,默认
active
-
exact: 精确匹配,表示必须精确匹配类名才生效
index.css
.active {
color: skyblue;
font-weight: 700;
}
index.js
/**
* 1. 导入react和react-dom
* 2. 创建 react 元素
* 3. 把 react 元素渲染到页面
*/
import React, { PureComponent } from 'react';
import ReactDOM from 'react-dom'
import Home from './views/Home'
import My from './views/My'
import Friend from './views/Friend'
import './index.css'
// 1. 导入核心组件
import { BrowserRouter as Router, Route, NavLink } from 'react-router-dom'
// Link:导航链接
// Route:指定路由规则
// HashRouter:整个路由组件,类似于 Vue 中的 VueRouter
class App extends PureComponent {
state = {
currentHash: ''
}
// hashchange 事件,即锚点值(hash值)改变就会触发
componentDidMount() {
// 注册事件
window.addEventListener('hashchange', () => {
console.log('hash值变了', window.location.hash);
this.setState({
currentHash: window.location.hash.slice(1)
})
console.log(window.location.hash.slice(1));
})
}
render() {
return (
// 2. 使用 Router 包裹整个应用
<Router>
<div>
<h3>App组件</h3>
<ul style={{display: 'flex'}}>
<li style={{listStyle: 'none', margin: '10px'}}>
{/*
通过Link组件提供导航链接
默认是模糊匹配
*/}
<NavLink style={{textDecoration: 'none'}} to="/home" exact>首页</NavLink>
</li>
<li style={{listStyle: 'none', margin: '10px'}}>
<NavLink style={{textDecoration: 'none'}} to="/my">我的音乐</NavLink>
</li>
<li style={{listStyle: 'none', margin: '10px'}}>
<NavLink style={{textDecoration: 'none'}} to="/friend">朋友</NavLink>
</li>
</ul>
{/* 4. 通过Route 指定路由规则 */}
<Route path="/home" component={Home}></Route>
<Route path="/my" component={My}></Route>
<Route path="/friend" component={Friend}></Route>
</div>
</Router>
)
}
}
// 渲染组件
// 18.2.0
// ReactDom.createRoot(document.getElementById('root')).render(<App />)
// 17.0.2
ReactDOM.render(<App />, document.getElementById('root'))
3.5 Route 组件
<Route path="/home" component={Home}></Route>
path 的说明:
-
默认情况下,/能够匹配任意/开始的路径
-
如果 path 的路径匹配上了,那么就可以对应的组件就会被 render
-
如果 path 没有匹配上,那么会 render null
-
如果没有指定 path,那么一定会被渲染
exact 的说明:
- exact 表示精确匹配某个路径
- 一般来说,如果路径配置了 /, 都需要配置 exact 属性
-
如果 path 不写,能够匹配所有的组件
Switch与404:
通常,我们会把Route
包裹在一个Switch
组件中
在Switch
组件中,不管有多少个路由规则匹配到了,都只会渲染第一个匹配的组件
通过Switch
组件非常容易的就能实现404错误页面的提示
一般会配合 Route 去实现一个 404 页面
index.js
/**
* 1. 导入react和react-dom
* 2. 创建 react 元素
* 3. 把 react 元素渲染到页面
*/
import React, { PureComponent } from 'react';
import ReactDOM from 'react-dom'
import Home from './views/Home'
import My from './views/My'
import Friend from './views/Friend'
import './index.css'
import NotFound from './views/NotFound';
// 1. 导入核心组件
import { BrowserRouter as Router, Route, NavLink, Switch } from 'react-router-dom'
// Link:导航链接
// Route:指定路由规则
// HashRouter:整个路由组件,类似于 Vue 中的 VueRouter
class App extends PureComponent {
state = {
currentHash: ''
}
// hashchange 事件,即锚点值(hash值)改变就会触发
componentDidMount() {
// 注册事件
window.addEventListener('hashchange', () => {
console.log('hash值变了', window.location.hash);
this.setState({
currentHash: window.location.hash.slice(1)
})
console.log(window.location.hash.slice(1));
})
}
render() {
return (
// 2. 使用 Router 包裹整个应用
<Router>
<div>
<h3>App组件</h3>
<ul style={{display: 'flex'}}>
<li style={{listStyle: 'none', margin: '10px'}}>
{/*
通过Link组件提供导航链接
默认是模糊匹配
*/}
<NavLink style={{textDecoration: 'none'}} to="/" exact>首页</NavLink>
</li>
<li style={{listStyle: 'none', margin: '10px'}}>
<NavLink style={{textDecoration: 'none'}} to="/my">我的音乐</NavLink>
</li>
<li style={{listStyle: 'none', margin: '10px'}}>
<NavLink style={{textDecoration: 'none'}} to="/friend">朋友</NavLink>
</li>
</ul>
{/* 4. 通过Route 指定路由规则 */}
{
/* Route
1. 如果path指定为 / ,那么所有 / 开头都能匹配
2. Route 添加 exact 属性,代表精确匹配
3. 如果 path 不写,能够匹配所有的组件
Switch:
如果将来有多个路由规则能够匹配到,只会让第一个渲染出来
*/
}
<Switch>
<Route exact path="/" component={Home}></Route>
<Route path="/my" component={My}></Route>
<Route path="/friend" component={Friend}></Route>
<Route component={NotFound}></Route>
</Switch>
</div>
</Router>
)
}
}
// 渲染组件
// 18.2.0
// ReactDom.createRoot(document.getElementById('root')).render(<App />)
// 17.0.2
ReactDOM.render(<App />, document.getElementById('root'))
NotFound.js
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
export default class NotFound extends Component {
render() {
return (
<div>
<p>NotFound 404</p>
<Link to="/">去首页</Link>
</div>
)
}
}
四、嵌套路由的配置
在React中,配置嵌套路由非常的简单,因为Route
就是一个组件,可以在任意想配置的地方进行配置
但是配置嵌套路由的时候,需要对路径进行处理,必须要先匹配到父级路由,才能匹配到子路由
// 通过/home可以匹配Home父组件 再通过/list匹配子组件
<Route path="/home/list" component={List} />
My.js
import React, { Component } from 'react'
import Rank from './Rank'
import SongList from './SongList'
import { Link, Route } from 'react-router-dom'
export default class My extends Component {
render() {
return (
<div>
<h5>我的音乐组件</h5>
{
/*
在数组中依旧可以使用 Link 指定路由的链接
Route 指定子路由的规则
不再使用Router组件
*/
}
<ul>
<li>
<Link to="/my/rank">排行榜</Link>
</li>
<li>
<Link to="/my/songlist">歌单</Link>
</li>
</ul>
{
/*
二级路由,path必须要包含一级路由
*/
}
<Route path="/my/rank" component={Rank}></Route>
<Route path="/my/songlist" component={SongList}></Route>
</div>
)
}
}
Rank.js
import React, { Component } from 'react'
export default class Rank extends Component {
render() {
return (
<div>排行榜音乐</div>
)
}
}
SongList.js
import React, { Component } from 'react'
export default class SongList extends Component {
render() {
return (
<div>歌单</div>
)
}
}
index.js
/**
* 1. 导入react和react-dom
* 2. 创建 react 元素
* 3. 把 react 元素渲染到页面
*/
import React, { PureComponent } from 'react';
import ReactDOM from 'react-dom'
import Home from './views/Home'
import My from './views/My'
import Friend from './views/Friend'
import './index.css'
import NotFound from './views/NotFound';
// 1. 导入核心组件
import { BrowserRouter as Router, Route, NavLink, Switch } from 'react-router-dom'
// Link:导航链接
// Route:指定路由规则
// HashRouter:整个路由组件,类似于 Vue 中的 VueRouter
class App extends PureComponent {
state = {
currentHash: ''
}
// hashchange 事件,即锚点值(hash值)改变就会触发
componentDidMount() {
// 注册事件
window.addEventListener('hashchange', () => {
console.log('hash值变了', window.location.hash);
this.setState({
currentHash: window.location.hash.slice(1)
})
console.log(window.location.hash.slice(1));
})
}
render() {
return (
// 2. 使用 Router 包裹整个应用
<Router>
<div>
<h3>App组件</h3>
<ul style={{display: 'flex'}}>
<li style={{listStyle: 'none', margin: '10px'}}>
{/*
通过Link组件提供导航链接
默认是模糊匹配
*/}
<NavLink style={{textDecoration: 'none'}} to="/" exact>首页</NavLink>
</li>
<li style={{listStyle: 'none', margin: '10px'}}>
<NavLink style={{textDecoration: 'none'}} to="/my">我的音乐</NavLink>
</li>
<li style={{listStyle: 'none', margin: '10px'}}>
<NavLink style={{textDecoration: 'none'}} to="/friend">朋友</NavLink>
</li>
</ul>
{/* 4. 通过Route 指定路由规则 */}
{
/* Route
1. 如果path指定为 / ,那么所有 / 开头都能匹配
2. Route 添加 exact 属性,代表精确匹配
3. 如果 path 不写,能够匹配所有的组件
Switch:
如果将来有多个路由规则能够匹配到,只会让第一个渲染出来
*/
}
<Switch>
<Route exact path="/" component={Home}></Route>
<Route path="/my" component={My}></Route>
<Route path="/friend" component={Friend}></Route>
<Route component={NotFound}></Route>
</Switch>
</div>
</Router>
)
}
}
// 渲染组件
// 18.2.0
// ReactDom.createRoot(document.getElementById('root')).render(<App />)
// 17.0.2
ReactDOM.render(<App />, document.getElementById('root'))
五、编程式导航
场景:点击登录按钮,登录成功后,通过代码跳转到后台首页,如何实现?
编程式导航:通过 JS 代码来实现页面跳转
history 是 React 路由提供的,用于获取浏览器历史记录的相关信息
- push(path):跳转到某个页面,参数 path 表示要跳转的路径
- go(n): 前进或后退到某个页面,参数 n 表示前进或后退页面数量(比如:-1 表示后退到上一页)
当使用 route 渲染组件时,Route 组件会自动给组件传递三个属性
- history
- location
- match
Friend.js
import React, { Component } from 'react'
export default class Friend extends Component {
render() {
return (
<div>
<p>我的朋友组件</p>
<button onClick={this.handleClick}>登录</button>
</div>
)
}
handleClick = () => {
// console.log(this.props);
this.props.history.push('/my')
}
}
六、react 动态路由参数与获取
一般会用在详情,比如文章详情、商品详情等
- 可以使用
:id
的方式来配置动态的路由参数
// 可以匹配 /users/1 /users/2 /users/xxx
<Route path="/users/:id" component={Users} />
- 在组件中,通过
props
可以接收到路由的参数
render(){
console.log(this.props.match.params)
}
index.js
/**
* 1. 导入react和react-dom
* 2. 创建 react 元素
* 3. 把 react 元素渲染到页面
*/
import React, { PureComponent } from 'react';
import ReactDOM from 'react-dom'
import Detail from './views/Detail'
import { BrowserRouter as Router, Link, Route } from 'react-router-dom'
class App extends PureComponent {
render() {
return (
<Router>
<h4>首页</h4>
<ul>
<li>
<Link to="/detail/1">商品详情</Link>
</li>
<li>
<Link to="/detail/2">商品详情</Link>
</li>
<li>
<Link to="/detail/3">商品详情</Link>
</li>
<li>
<Link to="/detail/4">商品详情</Link>
</li>
</ul>
<Route path="/detail/:id" component={Detail}></Route>
</Router>
)
}
}
// 渲染组件
// 18.2.0
// ReactDom.createRoot(document.getElementById('root')).render(<App />)
// 17.0.2
ReactDOM.render(<App />, document.getElementById('root'))
Detail.js
import React, { Component } from 'react'
export default class Detail extends Component {
render() {
const { match } = this.props
console.log(match);
return (
<div>详情页--{this.props.match.params.id}</div>
)
}
}