1、介绍
SPA概念:单页Web应用(single page web application),就是只有一张Web页面的应用,由一个主页面和多个页面片段组成,由JS控制页面片段的展示与否。整个页面不会刷新,只会做局部页面的更新操作,与服务端通过ajax进行异步交互。
React路由作用:React开发的是一个SPA应用,整个应用由一系列组件构成,所有的组件并非直接全部挂载,而是页面根据页面的操作,挂载相应的组件。组件的挂载与否的控制逻辑便是React路由完成。
2、实现原理
前端路由的实现,根本上是监控浏览地址的变化及历史记录,前端路由地址栏发生变更,不发起网路请求,但是会被监控到,根据不同的path,控制挂载不同的组件。
实现手段有两种,一种是基于history实现的BrowserRouter,一种是基于hash实现的HashRouter。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>前端路由</title>
</head>
<body>
<a href="http://www.atguigu.com" onclick="return push('/test1') ">push test1</a><br><br>
<button onClick="push('/test2')">push test2</button><br><br>
<button onClick="replace('/test3')">replace test3</button><br><br>
<button onClick="back()"><= 回退</button>
<button onClick="forword()">前进 =></button>
<script type="text/javascript" src="https://cdn.bootcss.com/history/4.7.2/history.js"></script>
<script type="text/javascript">
let history = History.createHashHistory()
function push (path) {
history.push(path)
return false
}
function replace (path) {
history.replace(path)
}
function back() {
history.goBack()
}
function forword() {
history.goForward()
}
history.listen((location) => {
console.log('请求路由路径变化了', location)
})
</script>
</body>
</html>
3、安装引入
reactor路由的官方实现有3种
- web:使用react做web端开发。
- native:使用react做原生应用开发(移动端)。
- anywhere:兼容web与native开发。
开发web端应用,一般采用web实现,这里使用react-router-dom 的版本5。
安装命令:npm install react-router-dom@5 。
4、入门使用
import React, { Component } from 'react';
import {Link,Route,BrowserRouter} from 'react-router-dom';
import './App.css';
class ComponentA extends Component {
render() {
return (
<div>组件A</div>
)
}
}
class ComponentB extends Component {
render() {
return (
<div>组件B</div>
)
}
}
export default class App extends Component {
render() {
return (
<div>
{/* 导航区和对应的展示区,需要使用BrowserRouter或HashRouter组件包裹起来 */}
<BrowserRouter>
<div style={{width:'300px',height:'100px',border:'1px solid'}}>
<h3>导航区域</h3>
{/*
Link组件,路由的导航组件。点击此组件时,页面上对应的组件将会被挂载。
*/}
<Link to="/a">展示组件A</Link>
<Link to="/b">展示组件B</Link>
</div>
<br />
<div style={{width:'300px',height:'100px',border:'1px solid'}}>
<h3>内容展示区域</h3>
{/*
Route组件,路由的展示组件。path属性与Link组件的to属性对应。
*/}
<Route path="/a" component={ComponentA}/>
<Route path="/b" component={ComponentB}/>
</div>
</BrowserRouter>
</div>
)
}
}
效果:地址栏虽然发生了改变,但是浏览器并未发起网络请求,React路由只是根据地址栏变化,挂载了不同的组件。
使用BrowserRouter或HashRouter组件时,一劳永逸的办法是写在src\index.js 里面。
import React from 'react';
import ReactDOM from 'react-dom/client';
import {BrowserRouter} from 'react-router-dom'
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
BrowserRouter与HashRouter组件
二者浏览器地址栏展示方式不同,BrowserRouter地址栏域名与path间使用/ 分割;HashRouter地址栏域名与path间使用/#/ 分割,与锚点链接一致。
效果:BrowserRouter(左),HashRouter(右)
5、路由组件&一般组件
一般组件:直接挂载在页面的组件。
<ComponentA/>
路由组件:借助路由方式挂载到页面的组件。
<Route path="/a" component={ComponentA}/>
区别:
- 挂载到页面的写法不同,如上。
- 按照约定,在项目中存放的位置不同,一般组件通常存放在
components 目录,路由组件通常存放在pages 目录(不绝对)。 - 默认接收到的props属性值不同。一般组件传什么就接收到什么,路由组件的props属性固定接收到
history 、location 、match 三个值。
history:
goBack: ? goBack() //后退到上一页面
goForward: ? goForward() //前进到下一页面
go: ? go(n) //后退或前进n个页面,负数后退,正数前进
push: ? push(path, state) //浏览记录入栈新页面,且浏览器地址栏改为新页面地址,state可选
replace: ? replace(path, state) //浏览末位记录替换为新页面,且浏览器地址栏改为新页面地址,state可选
location:
pathname: "" //路径
search: "" //路由组件search参数
state: undefined //路由组件state参数
match:
params: {} //路由组件params参数
path: "" //路径
url: "" //url
6、NavLink组件
NavLink导航组件被点击时,会自动为当前组件的html标签添加class='active' 属性,只需要给类名active 编写css样式,页面就能友好展示当前正被点击的导航组件。
使用NavLink组件替换Link组件
{/* 当此组件被点击时,会自动为其添加class=active */}
<NavLink to="/a">展示组件A</NavLink>
<NavLink to="/b">展示组件B</NavLink>
为class=active编写样式
.active{
font-size: 14px;
color: #fff;
height: 44px;
padding: 0 15px;
background-color: #07c160;
border: 1px solid #07c160;
line-height: 1.2;
text-align: center;
border-radius: 2px;
cursor: pointer;
transition: opacity 0.2s;
outline: none;
position: relative;
}
效果
NavLink自定义点击事件类名
{/* 当此组件被点击时,会自动为其添加class=haha */}
<NavLink activeClassName="haha" to="/a">展示组件A</NavLink>
7、Switch组件
当浏览器地址发生变化,路由组件会被全部遍历一遍,满足匹配的路由组件均会被挂载。
路由组件
<Route path="/a" component={ComponentA}/>
<Route path="/b" component={ComponentB}/>
<Route path="/b" component={ComponentB}/>
效果
使用Switch将路由组件包裹起来,在当前Switch中,当匹配到一个满足条件的路由组件,便停止遍历,也可提高匹配效率。
路由组件
<Switch>
<Route path="/a" component={ComponentA}/>
<Route path="/b" component={ComponentB}/>
<Route path="/b" component={ComponentB}/>
</Switch>
效果
如果存在多个Switch,每个Switch中的路由组件都会被遍历匹配一次
路由组件
<Switch>
<Route path="/a" component={ComponentA}/>
<Route path="/b" component={ComponentB}/>
</Switch>
<Switch>
<Route path="/a" component={ComponentA}/>
</Switch>
{/* 两个Switch都会被匹配一次,所以ComponentA被匹配到时会被挂载2次 */}
效果
8、路由匹配模式
模糊匹配:React路由默认使用模糊匹配。
{/* 导航组件 */}
<NavLink to="/a/a-1">展示组件A</NavLink>
<Switch>
{/* 路由组件,因为默认模糊匹配,所以此组件能匹配上面的导航组件 */}
<Route path="/a" component={ComponentA}/>
</Switch>
精准匹配:需要手动开启(精准匹配开启需要慎重,可能会导致二级路由失效)。
{/* 导航组件 */}
<NavLink to="/a/a-1">展示组件A</NavLink>
<Switch>
{/* 路由组件,使用exact属性开启精准匹配,此时此组件不能匹配上面的导航组件 */}
<Route exact={true} path="/a" component={ComponentA}/>
{/* 开启精准匹配简写方式 */}
<Route exact path="/a" component={ComponentA}/>
</Switch>
Redirect组件(默认匹配):当Switch中所有路由组件均无法匹配时,被默认挂载的组件。
<Switch>
<Route exact path="/a" component={ComponentA}/>
<Route path="/b" component={ComponentB}/>
{/* 在本文档中,浏览器默认打开 http://localhost:3000/ ,React路由会默认开始匹配 / ,所有路由组件均无法匹配,便使用Redirect中匹配的路由组件 */}
<Redirect to="/a"/>
</Switch>
9、路由嵌套
通过路由的方式挂载的组件,其内部又通过路由挂载组件,就形成路由嵌套。
import React, { Component } from 'react';
import {NavLink,Route,Switch,Redirect} from 'react-router-dom';
import './index.css';
export default class Levels extends Component {
render() {
return (
<div>
<div className='container'>
<h3>导航区域</h3>
<NavLink to="/a">展示组件A</NavLink>
<NavLink to="/b">展示组件B</NavLink>
</div>
<br />
<div className='container'>
<h3>1级路由展示区域</h3>
<Switch>
<Route path="/a" component={ComponentA}/>
<Route path="/b" component={ComponentB}/>
</Switch>
</div>
</div>
)
}
}
class ComponentA extends Component {
render() {
return (
<div>
{/*
1.注册子路由时要写上父路由的path值
2.路由的匹配是按照注册路由的顺序进行的(先匹配挂载父路由组件,再匹配挂载子路由组件,父路由匹配依赖模糊匹配)
*/}
<NavLink to="/a/msg">消息</NavLink>
<NavLink to="/a/myself">个人中心</NavLink>
<br />
<div className='container2'>
<h3>2级路由展示区域</h3>
<Switch>
<Route path="/a/msg" component={Msg}/>
<Route path="/a/myself" component={Myself}/>
</Switch>
</div>
</div>
)
}
}
class ComponentB extends Component {
render() {
return (
<div>组件B</div>
)
}
}
class Msg extends Component {
render() {
return (
<div>消息组件</div>
)
}
}
class Myself extends Component {
render() {
return (
<div>个人中心组件</div>
)
}
}
效果
10、向路由组件传参
params参数
import React, { Component } from 'react';
import {NavLink,Route,Switch,Redirect} from 'react-router-dom';
import './index.css';
export default class Params extends Component {
render() {
const person = {
name:"张三",
age:18
}
return (
<div>
<div className='container'>
<h3>导航区域</h3>
{/* params参数:直接在导航组件to属性里面传参 */}
<NavLink to={`/a/${person.name}/${person.age}`}>展示组件A</NavLink>
</div>
<br />
<div className='container'>
<h3>展示区域</h3>
<Switch>
{/* params参数:路由组件path后面,使用:符号接收参数 */}
<Route path="/a/:name/:age" component={ComponentA}/>
</Switch>
</div>
</div>
)
}
}
class ComponentA extends Component{
render(){
// params参数:存放在 this.props.match.params 里面,已经被自动封装成对象。
const {name,age} = this.props.match.params;
return(
<div>
<p>组件A</p>
<p>接收到的params数据:{name},{age}</p>
</div>
)
}
}
效果
search参数
{/* search参数:导航组件在?符号后面传参 */}
<NavLink to="/a?name=张三&age=18">展示组件A</NavLink>
{/* search参数:路由组件无需修改,正常引入即可 */}
<Route path="/a" component={ComponentA}/>
class ComponentA extends Component{
render(){
/**
* search参数:存放在 this.props.location.search 里面
* 接收到的是urlencoded编码字符串,可以借助第三方库解析结果。如querystring库
* 因为本参数存在中文,接收到的结果
* 可能为:?name=张三&age=18
* 也可能为:?name=%E5%BC%A0%E4%B8%89&age=18
*/
const p = this.props.location.search;
return(
<div>
<p>组件A</p>
<p>接收到的params数据:{p}</p>
</div>
)
}
}
state参数
params、search方式传参使用方便,但是参数会显示在地址栏,若传递的参数不想被用户看到,可使用state方式传参。
{/* state参数:导航组件to属性中使用state传参 */}
<NavLink to={{pathname:'/a',state:{name:'tom',age:18}}}>展示组件A</NavLink>
{/* state参数:路由组件无需修改,正常引入即可 */}
<Route path="/a" component={ComponentA}/>
class ComponentA extends Component{
render(){
/**
* state参数:存放在 this.props.location.state 里面
* 并且刷新也可以保留住参数(仅针对BrowserRouter)
*/
const {name,age} = this.props.location.state;
return(
<div>
<p>组件A</p>
<p>接收到的params数据:{name},{age}</p>
</div>
)
}
}
演示
11、push&replace
浏览器历史记录存储方式为栈结构(先进后出),React路由仅改变地址栏,未发起网络请求,但也会生成保留历史记录。
push:入栈一条页面记录,浏览器地址栏改为新入栈的地址(默认),会留下历史记录,可以回退。
replace:直接替换浏览器当前展示的页面记录,不会留下历史记录,不能回退(应用场景:登录成功不能回退到登录页面、支付成功不能返回待支付页面等)。
演示push导航组件
{/* 为导航组件添加push属性,标识为push操作 */}
<NavLink push to="/a">展示组件A</NavLink>
{/* 导航组件不添加push属性,默认为push操作 */}
<NavLink to="/b">展示组件B</NavLink>
演示replace导航组件
{/* 为导航组件添加replace属性,标识为replace操作 */}
<NavLink replace to="/a">展示组件A</NavLink>
<NavLink replace to="/b">展示组件B</NavLink>
12、编程式路由导航
借助导航组件组件(Link、NavLink等)可以控制路由组件的挂载逻辑,也能控制页面回退、前进访问历史记录,这些操作都需要点击触发导航组件,在某些情况下,页面没有导航组件可以点击,也需要实现这些功能。如点击网站logo图标跳转网站首页、支付成功3秒后自动挂载订单详情组件等。
借助路由组件提供的属性中的API,也能动态实现导航组件的功能。
history:
goBack: ? goBack() //后退到上一页面
goForward: ? goForward() //前进到下一页面
go: ? go(n) //后退或前进n个页面,负数后退,正数前进
push: ? push(path, state) //浏览记录入栈新页面,且浏览器地址栏改为新页面地址,state可选
replace: ? replace(path, state) //浏览末位记录替换为新页面,且浏览器地址栏改为新页面地址,state可选
location:
pathname: "" //路径
search: "" //路由组件search参数
state: undefined //路由组件state参数
match:
params: {} //路由组件params参数
path: "" //路径
url: "" //url
withRouter:当一般组件中想要利用路由组件的history、location、match属性动态实现导航组件功能时,需要借助withRouter。
//导入withRouter
import {withRouter} from 'react-router-dom'
class MyComponent extends Component {
//省略组件内容,使用路由组件的属性
}
//导出组件时,使用withRouter修饰组件,此组件就能使用路由组件所特有的API
export default withRouter(MyComponent)
13、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可以用于解决一些路径错误相关的问题。
|