React 路由
尚硅谷 2021 版 React 技术全家桶全套完整版(零基础入门到精通/男神天禹老师亲授)
路由 SPA
- 单页 Web 应用(Single Page Web Application)
- 整个应用只有一个完整的页面
- 点击页面中的链接不会刷新页面,只会做页面的局部更新
- 数据都需要通过 ajax 请求获取,并在前端异步展现
1. 什么是路由
- 一个路由就是一个映射关系(key:value)
- key 为路径,value 可能是 function 或 component
2. 路由分类
- 后端路由
- 理解:value 是 function,用来处理客户端提交的请求
- 注册路由:
router.get(path, function(req, res))
- 工作过程:当 node 接收到一个请求时,根据请求路径找到匹配的路由,调用路由中的函数来处理请求,返回响应数据
- 前端路由
- 理解:value 是 component,用于展示页面内容
- 注册路由:
<Route path="/test" component={Test}>
- 工作流程:当浏览器的 path 变为
/test
时,当前路由组件就会变为 Test 组件
<a href="http://www.baidu.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.createBrowserHistory() //方法一,直接使用H5推出的history身上的API
let history = History.createHashHistory() //方法二,hash值(锚点)
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>
样式丢失问题:
- public/index.html 中引入样式时不写
./
写/
(常用) - public/index.html 中引入样式时不写
./
写%PUBLIC_URL%
(常用) - 使用 HashRouter
<link rel="stylesheet" href="./bootstrap.css" />
<!-- 1.去掉. -->
<link rel="stylesheet" href="/bootstrap.css" />
<!-- 2.使用%PUBLIC_URL% -->
<link rel="stylesheet" href="%PUBLIC_URL%/bootstrap.css" />
<!-- 3.把BrowserRouter改为HashRouter -->
<BrowserRouter>
<App />
</BrowserRouter>
<HashRouter>
<App />
</HashRouter>
路由基本使用
react-router-dom
- react 的一个插件库
- 专门用来实现一个 SPA 应用
- 基于 react 的项目基本都会用到此库
https://react-router.docschina.org/
基本使用:
-
明确界面中的导航区、展示区
-
导航区 a 标签改为 Link 标签
<Link to="/xxx">Demo</Link>
-
展示区写 Route 标签进行路径的匹配
<Route path="/xxx" component={Demo}>
-
<App />
最外侧包裹一个<BrowserRouter />
或<HashRouter />
{/* 原生html中,靠<a>跳转不同的页面 */}
{/* <a className="list-group-item active" href="./about.html">
About
</a>
<a className="list-group-item" href="./home.html">
Home
</a> */}
{/* 在React中靠路由链接实现切换组件-编写路由链接 */}
<Link className="list-group-item" to="/about">
About
</Link>
<Link className="list-group-item" to="/home">
Home
</Link>
{/* 注册路由 */}
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
路由组件与一般组件区别:
-
写法不同:
一般组件:
<Demo />
路由组件:
<Route path="/demo" component={Demo} />
-
存放位置不同:
一般组件:components
路由组件:pages
-
接收到的 props 不同:
一般组件:写组件标签时传递了什么,就能收到什么
路由组件:接收到三个固定属性
history
、location
、match
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 可以实现路由链接的高亮,通过
activeClassName
指定样式名 - 标签体内容是一个特殊的标签属性
- 通过
this.props.children
可以获取标签体内容
{...this.props}
不仅可以把在内联标签属性添加过来,还能把标签体的内容(children)拿过来填写在标签体内部
import React, { Component } from 'react'
import { NavLink } from 'react-router-dom'
export default class MyNavLink extends Component {
render() {
return <NavLink activeClassName="active" className="list-group-item" {...this.props} />
}
}
// 组件调用
<MyNavLink to="/about">About</MyNavLink>
Switch 基本使用
- 通常情况下,path 和 component 是一一对应的关系
- Switch 可以提高路由匹配效率(单一匹配)
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Route path="/home" component={About} />
</Switch>
嵌套路由(匹配 重定向)
模糊匹配
to="/home/a/b"
可以匹配上path="/home"
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home/a/b">Home</MyNavLink>
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
</Switch>
- 默认使用的是模糊匹配(【输入的路径】必要要包含【匹配的路径】,且顺序要一致)
- 开启严格匹配
<Route exact path="/about" component={About} />
- 严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
重定向
- 一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到 Redirect 指定的路由
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Redirect to="/about" />
</Switch>
嵌套路由
- 注册子路由时要写上父路由的 path 值
- 路由的匹配时按照注册路由的顺序进行的
<MyNavLink to="/home/news">News</MyNavLink>
<MyNavLink to="/home/message">Message</MyNavLink>
<Switch>
<Route path="/home/news" component={News} />
<Route path="/home/message" component={Message} />
<Redirect to="/home/news" />
</Switch>
向路由组件传递参数
state = {
messageArr: [
{ id: '01', title: '消息1' },
{ id: '02', title: '消息2' },
{ id: '03', title: '消息3' },
],
}
<ul>
{messageArr.map(msgObj => {
return (
<li key={msgObj.id}>
<Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
</li>
)
})}
</ul>
-
params 参数
路由链接(携带参数):
<Link to="/demo/test/tom/18">详情</Link>
注册路由(声明接收):
<Route path="/demo/test/:name/:age" component={Test} />
接收参数:
const {name, age} = this.props.match.params
{/* 向路由组件传递params参数 */}
<Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
{/* 声明接收params参数 */}
<Route path="/home/message/detail/:id/:title" component={Detail} />
// 接收params参数
const { id, title } = this.props.match.params
-
search 参数
路由链接(携带参数):
<Link to="/demo/test?name=tom&age=18">详情</Link>
注册路由(无需声明,正常注册即可):
<Route path="/demo/test" component={Test} />
接收参数:
const {search} = this.props.location; const {name, age} = qs.parse(search.slice(1))
备注:获取到的 search 是 urlencoded 编码字符串,需要借助 querystring 解析
{/* 向路由组件传递search参数 */}
<Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>
{/* search参数无需声明接收 */}
<Route path="/home/message/detail" component={Detail} />
// 接收search参数
const { search } = this.props.location
const { id, title } = qs.parse(search.slice(1))
-
state 参数
路由链接(携带参数):
<Link to={{path:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>
注册路由(无需声明,正常注册即可):
<Route path="/demo/test" component={Test} />
接收参数:
const {name, age} = this.props.location.state
备注:刷新也可以保留住参数
{/* 向路由组件传递state参数 */}
<Link to={{ pathname: '/home/message/detail', state: { id: msgObj.id, title: msgObj.title } }}>{msgObj.title}</Link>
{/* state参数无需声明接收 */}
<Route path="/home/message/detail" component={Detail} />
// 接收state参数
const { id, title } = this.props.location.state || {}
编程式路由导航
默认是 push 模式
<MyNavLink to="/home/news">News</MyNavLink>
<MyNavLink replace to="/home/message">Message</MyNavLink>
借助 this.props.history
对象上的 API 对操作路由跳转、前进、后退
this.props.history.push()
this.props.history.replace()
this.props.history.gpBack()
this.props.history.goForward()
this.props.history.go()
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 })
}
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> */}
<button
onClick={() => {
this.pushShow(msgObj.id, msgObj.title)
}}
>
push操作
</button>
<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} />
</div>
)
}
}
withRouter
- withRouter 可以加工一般组件,让一般组件具备路由组件所特有的API
- withRouter 的返回值是一个新组件
import React, { Component } from 'react'
import { withRouter } from 'react-router-dom'
class Header extends Component { }
export default withRouter(Header)
BrowerRouter 与 HashRouter 的区别
-
底层原理不一样
BrowerRouter 使用的是 H5 的 history API,不兼容 IE9 以下版本
HashRouter 使用的是 URL 哈希值
-
path 表现形式不一样
BrowerRouter 的路径中没有 #,例如:
localhost:3000/demo/test
HashRouter 的路径中包含 #,例如:
localhost:3000/#/demo/test
-
刷新后对路由 state 参数影响
BroserRouter 没有任何影响,因为 state 保存在 history 对象中
HashRouter 刷新后会导致路由 state 参数丢失
-
备注:HashRouter 可以用于解决一些路径错误相关的问题