【React自学笔记03】React从山脚向山腰的攀登
五、React路由
5.1 路由相关理解
5.1.1 SPA的理解
-
单页Web应用(single page web application,SPA)。
-
整个应用只有一个完整的页面。
-
点击页面中的链接不会刷新页面,只会做页面的局部更新。
-
数据都需要通过ajax请求获取, 并在前端异步展现。
-
单页面、多组件
5.1.2 路由的理解
1. 什么是路由?
-
一个路由就是一个映射关系(key:value)
-
key为路径(path), value可能是function或component
2. 路由分类
-
后端路由:
-
理解: value是function, 用来处理客户端提交的请求
-
注册路由: router.get(path, function(req, res))
-
工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
-
-
前端路由:
-
浏览器端路由,value是component,用于展示页面内容
-
注册路由: < Route path="/test" component={Test}>
-
工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件
-
5.2 react-router-dom
1. 理解react-router-dom
-
react的一个插件库。
-
专门用来实现一个SPA应用。
-
基于react的项目基本都会用到此库。
2. 储备知识
-
路由Route和路由器Router
-
安装 react-router-dom: npm i react-router-dom@5
-
路由的原理:点击导航区不同的导航选项(路由链接),引起历史记录中路径的变化 ,路径变化被前端路由器所监测到,进行匹配组件,从而展示。
-
区分导航区和展示区,react中靠路由链接实现切换组件
3. 路由组件的基本使用
案例:点击不同导航区,显示不同组件
-
引入import {Link} from 'react-router-dom'
-
改造导航区,点击路由链接改变浏览器路径
-
编写路由链接
<Link className="list-group-item active" to="/about">About</Link>
-
to后面用字符串 /小写
-
link标签外侧要包有Router
import { Link, Router } from 'react-router-dom' <Router> <Link className="list-group-item active" to="/about">About</Link> <Link className="list-group-item active" to="/home">Home</Link> </Router>
-
路由器分为两种:BrowserRouter和HashRouter,先指定使用BrowserRouter
-
-
监测路径变化,根据路径匹配组件
-
引入组件
import Home from './components/Home' import About from './components/About'
-
引入路由
import { Link, Router,Route } from 'react-router-dom'
-
在展示区写注册路由
<BrowserRouter> <Route path="/home" component={Home} /> <Route path="/about" component={About} /> </BrowserRouter>
-
整个应用得用一个路由器管理
//index.js 将路由器包裹到root标签外侧 import { BrowserRouter } from "react-router-dom"; root.render(<BrowserRouter><App /></BrowserRouter>) //将App.js中的BrowserRouter相关去掉
-
💕 总结:
-
明确好界面中的导航区、展示区
-
导航区的a标签改为Link标签
< Link to="/xxx">Demo< /Link>
-
展示区写Route标签进行路径匹配
< Route path="/xxx" component={Demo} />
-
< App>的最外侧包裹了一个< BrowserRouter>或< HashRouter>
——————————————记录于2022.05.23——————————————
4. 路由组件和一般组件的区别
写法不同:
-
一般组件 < Demo />
-
路由组件 < Route path="/xxx" component={Demo} />
存放位置:
-
将所有路由组件放到pages文件夹下
-
组件放到components文件夹下
接收到的props不同
-
一般组件:写组件标签时传递了什么,就能收到什么
-
路由组件:接收到三个固定的属性history、location、match
history:
- go: ƒ go(number) 前进几步
- goBack: ƒ goBack()
- goForward: ƒ goForward()
- push: ƒ push(path, state)
- replace: ƒ replace(path, state)
location:
- pathname: "/home/message"
- search: ""
- state: null
match:
- params: {}
- path: "/home/message"
- url: "/home/message"
5. 当前导航栏高亮
activeClassName默认为active
import { NavLink, Route } from 'react-router-dom'
<NavLink className="list-group-item" to="/about">About</NavLink>
自定义高亮样式
<NavLink activeClassName="customize" className="list-group-item" to="/about">About</NavLink>
/*在public/index.html中新增如下样式 */
.customize{
background-color:orange !important;
color:white !important;
}
6. 封装navLink
在自定义组件标签中把标签名改为标签体,props接收标签属性、标签体内容中也是特殊的标签属性,在this.props.children中
在组件文件中修改代码
//App.js
<MyNavLink to="/home">Home</MyNavLink>
<MyNavLink to="/about">About</MyNavLink>
//index.js
<NavLink activeClassName='customize' className="list-group-item" {...this.props}></NavLink>
💕 总结:
-
NavLink可以实现路由链接的高亮通过activeclassName指定样式名
-
标签体内容是一个特殊的标签属性
-
通过this.props.children可以获取组件标签体内容
7. Switch组件的使用
注册路由有多个的时候,用< Switch>< /Switch>包裹所有注册路由,仅匹配一次,效率较高,匹配到了就不会再往下匹配了
import { Route, Switch } from 'react-router-dom'
<Switch>
<Route path="/home" component={Home} />
<Route path="/about" component={About} />
</Switch>
💕 总结:
- 通常情况下,path和component是一一对应的关系
- Switch可以提高路由匹配效率(单一匹配)
8. 样式丢失问题
如果是多层路径,使用BrowserRouter路由器,使用相对路径./css/bootstrap.css的形式引入路径,服务器默认显示public文件夹下的index.html文件
解决方案
-
index.html文件中去掉. 直接/css/bootstrap.css
-
使用%PUBLIC_URL%/css/bootstrap.css
-
修改路由模式:将BrowserRouter改为HashRouter
9. 严格匹配模式
默认模糊匹配:(原则:给的不能比要的少)输入的路径必须要包含匹配的路径 顺序要一致
开启严格模式
<Route exact={true} path="/about" component={About}/>
简写
<Route exact path="/about" component={About}/>
严格匹配不要随意开启,需要再开,有时候开启会导致无法继续匹配二级路由
10. 路由重定向
-
引入
import { Route, Switch,Redirect } from 'react-router-dom'
-
兜底标签,写在< Route/>标签的最下面,当所有路由都无法匹配时,跳转到Redirect指向的路由中
<Switch> <Route path="/home" component={Home} /> <Route path="/about" component={About} /> <Redirect to="/home" /> </Switch>
——————————————记录于2022.05.24——————————————
5.3 嵌套路由(二级路由)
5.3.1 基本使用
-
注册子路由时需要写上父路由的path值
-
路由的匹配是按照注册路由的顺序进行的
//Home.jsx
import { Redirect, Route } from 'react-router-dom'
import MyNavLink from '../../components/MyNavLink'
import Massage from './Massage'
import News from './News'
<div>
{/* 导航区 */}
<ul className="nav nav-tabs">
<li>
{/* <a className="list-group-item" href="./home-news.html">News</a> */}
<MyNavLink to="/home/news">news</MyNavLink>
</li>
<li>
<MyNavLink to="/home/message">message</MyNavLink>
{/* <a className="list-group-item active" href="./home-message.html">Message</a> */}
</li>
</ul>
<div>
{/* 展示区 */}
<Route path="/home/news" component={News} />
<Route path="/home/message" component={Massage} />
<Redirect to="/home/news" />
</div>
</div>
5.3.2 向路由组件传参
1. 向路由组件传递params参数
步骤
(1)导航区携带params参数
this.state.msgList.map((msg) => {
return (
<li key={msg.id}>
<Link to={`/home/message/detail/${msg.id}/${msg.title}`} >{msg.title}</Link>
</li>
)
}
(2)视图区声明接收
<Route path="/home/message/detail/:id/:title" component={Detail} />
(3)Detail中接收params参数
this.props=>{history:{...},location:{...},match:{params:{id:"01",title:"msg01"}}}
const { id, title } = this.props.match.params
完整代码:
//Massage.jsx
import React, { Component } from 'react'
import { Link, Route } from 'react-router-dom'
import Detail from './Detail'
export default class Massage extends Component {
state = {
msgList: [
{ id: '01', title: 'msg1' },
{ id: '02', title: 'msg2' },
{ id: '03', title: 'msg3' }
]
}
render() {
return (
<div>
<ul>
{
this.state.msgList.map((msg) => {
return (
<li key={msg.id}>
<Link to={`/home/message/detail/${msg.id}/${msg.title}`} >{msg.title}</Link>
</li>
)
})
}
</ul>
<hr />
<Route path="/home/message/detail/:id/:title" component={Detail} />
</div>
)
}
}
//Detail.jsx
import React, { Component } from 'react'
const detailData = [
{
id: '01', content: '你好,世界'
}, {
id: '02', content: '你好,中国'
}, {
id: '03', content: '你好,未来'
}
]
export default class Detail extends Component {
render() {
const { id, title } = this.props.match.params
const findResult = detailData.find((detailObj) => {
return detailObj.id === id
})
// console.log(findResult);
return (
<ul>
<li>ID:{id}</li>
<li>TITLE:{title}</li>
<li>CONTENT:{findResult.content}</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
2. 向路由组件传递search参数
(1)导航区携带search参数
<Link to={`/home/message/detail/?id=${msg.id}&title=${msg.title}`} >{msg.title}</Link>
(2)视图区的search参数无需声明接收,正常注册路由即可
<Route path="/home/message/detail" component={Detail} />
(3)组件中接收search参数
import qs from 'qs'
const { search } = this.props.location
const { id, title } = qs.parse(search.slice(1))
💕 总结:
1.qs的两个方法
-
将对象转为字符串格式
- urlencoded编码:key=value&key=value
import qs from 'qs' let obj = { name: 'tom', age: 18 } console.log(qs.stringify(obj));
-
将字符串转为对象格式
import qs from 'qs' let str = 'carName=奥迪&price=199' console.log(qs.parse(str));
2.search参数
-
路由链接(携带参数):< Link to ='/demo/test/?name='tom'&age=18>详情< / Link>
-
注册路由(声明接收):< Route path="/demo/test" component={Test}>
-
接收参数:const {name,age}=this.props.location.search
注意:获取到的search是urllencoded编码字符串,需要借助qs解析
3. state参数
(1)导航区携带state参数
<Link to={{ pathname: '/home/message/detail', state: { id: msg.id, title: msg.title } }} />
(2)视图区的state参数无需声明接收,正常注册路由即可
<Route path="/home/message/detail" component={Detail} />
(3)组件中接收state参数
const { id, title } = this.props.location.state
💕 总结:
-
路由链接(携带参数):< Link to ={pathname:'/demo/test/' ,state:{name:'tom',age:18}}>详情< / Link>
-
注册路由(声明接收):< Route path="/demo/test" component={Test}>
-
接收参数:const {name,age}=this.props.location.state
-
注意:地址栏隐藏参数,刷新也可以保留参数
5.4 路由跳转的两种模式
push留下痕迹(默认)
replace不留下痕迹(强制开启 replace)
——————————————记录于2022.05.25——————————————
5.5 编程式路由导航
编程式导航的三种携带参数的写法
pushShow = (id, title) => {
console.log(this.props);
// 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({ pathname: '/home/message/detail', state: { id: id, title: title } })
}
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({ pathname: '/home/message/detail', state: { id: id, title: title } })
}
<button onClick={() => { this.pushShow(msg.id, msg.title) }}>push查看</button>
<button onClick={() => { this.replaceShow(msg.id, msg.title) }}>replace查看</button>
{/* <Route path="/home/message/detail/:id/:title" component={Detail} /> */}
{/* <Route path="/home/message/detail" component={Detail} /> */}
{/* <Route path="/home/message/detail" component={Detail} /> */}
前进后退功能
back = () => {
this.props.history.goBack()
}
forward = () => {
this.props.history.goForward()
}
<button onClick={this.back}>回退</button>
<button onClick={this.forward}>前进</button>
前进几步/后退几步
go = () => {
this.props.history.go(-2)
}
<button onClick={this.go}>go</button>
计时器2s自动跳转
componentDidMount(){
setTimeout(()=>{
this.props.history.push('/home/message')
},2000)
}
💕 总结:
借助this.prosp.history对象上的API对操作路由跳转、前进、后退
- this.prosp.history.push()
- this.prosp.history.replace()
- this.prosp.history.goBack()
- this.prosp.history.goForward()
- this.prosp.history.go()
5.6 withRouter的使用
只有路由组件才有history、location、match属性
withRouter能够接收一般组件,使得一般组件具备路由组件才有的API
withRouter的返回值是一个新组件
//Header.jsx
import React, { Component } from 'react'
import { withRouter } from 'react-router-dom'
class Header extends Component {
back = () => {
this.props.history.goBack()
}
go = () => {
this.props.history.go(-2)
}
forward = () => {
this.props.history.goForward()
}
render() {
return (
<div className="page-header">
<h2>React Router Demo</h2>
<button onClick={this.back}>回退</button>
<button onClick={this.forward}>前进</button>
<button onClick={this.go}>go</button>
</div>
)
}
}
export default withRouter(Header)
5.7 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可以用于解决一些路径错误相关的问题。
——————————————记录于2022.05.26——————————————
六、React Router6
6.1 概述
1. React Router以三个不同的包发布到npm 上,它们分别为:
-
1.react-routert路由的核心库,提供了很多的:组件、钩子。
-
2.react-router-domt包含react-router所有内容,并添加一些专门用于DOM的组件,例如< BrowserRouter>等。
-
3.react-router-native,包括react-router所有内容,并添加一些专门用于ReactNative的API,例如: < NativeRouter>等。
2. 与React Router 5.x版本相比,改变了什么?
-
1.内置组件的变化:移除< Switch/>,新增< Routes/>等。
-
2.语法的变化: component={About}变为element={< About/>}等。
-
3.新增多个hook: useParams 、useNavigate、useMatch 等。
-
4.官方明确推荐函数式组件了
6.2 Component
6.2.1 < Routes/>与< Route/>
新建项目 create-react-app hello
安装路由 npm i react-router-dom
import React from 'react'
import { NavLink, Routes, Route, Navigate } from 'react-router-dom'
import Header from './components/Header'
import About from './pages/About'
import Home from './pages/Home'
export default function App() {
return (
<div>
<div className="row">
<Header />
</div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 导航区 */}
<NavLink className="list-group-item" to="/about">About</NavLink>
<NavLink className="list-group-item" to="/home">Home</NavLink>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 展示区 */}
<Routes>
<Route path="/about" element={<About />} />
<Route path="/home" element={<Home />} />
<Route path="*" element={<Navigate to="/about" />} />
</Routes>
</div>
</div>
</div>
</div>
</div>
)
}
💕 总结:
-
< Switch>不需要重复查找,只要找到匹配项就不会继续往下匹配,提高了路由的匹配效率,在路由的v6版本中已经使用< Routes> 标签来代替
-
< Route>标签发生变化,新写法需要注意
-
< Route>相当于一个if语句,如果其路径与当前URL匹配,则呈现其对应的组件。
-
< Route caseSensitive>属性用于指定:匹配时是否区分大小写(默认为 false)
6.2.2 < Navigate/>
-
路由重定向的新写法:<Route path="*" element={< Navigate to="/about" />} />
-
navigate 只要渲染就会修改路径,引起视图切换
import React, { Fragment } from 'react'
import { Navigate } from 'react-router-dom'
export default function Home() {
const [sum, setSum] = React.useState(1)
function change() {
setSum(sum => sum + 1)
}
return (
<Fragment>
<h3>我是Home的内容</h3>
{
sum === 1 ? <h2>sum的值为{sum}</h2> : <Navigate to="/about" />
}
<button onClick={change}>点我将sum变为2</button>
</Fragment>
)
}
6.2.3 NavLink高亮
自定义导航样式:在className中调用函数,该函数自动接收一个参数,该参数为{isActive:true/false},同时该函数需要一个return返回值,作为样式className的值
//在App函数组件中定义computedClassName函数用来动态改变类样式
function computedClassName({ isActive }) {
return isActive ? 'list-group-item xjtu' : 'list-group-item'
}
{/* 导航区:负责路由跳转 */}
<NavLink className={computedClassName} to="/about">About</NavLink>
<NavLink className={computedClassName} to="/home">Home</NavLink>
6.2.4 路由表的使用
-
更改注册路由的编写方式 ——路由表
-
定义路由表 const element = useRoutes(routes)
-
将routes抽离为一个单独的文件
-
展示区使用
-
//routes.js
import { Navigate } from "react-router-dom"
import About from "../pages/About"
import Home from "../pages/Home"
export default [
{
path: '/about',
element: <About />
}, {
path: '/home',
element: <Home />
}, {
path: '*',
element: < Navigate to="/about" />
}
]
//App.js
import React from 'react'
import { NavLink, useRoutes } from 'react-router-dom'
import Header from './components/Header'
import routes from './routes/index'
export default function App() {
const element = useRoutes(routes)
function computedClassName({ isActive }) {
return isActive ? 'list-group-item xjtu' : 'list-group-item'
}
return (
<div>
<div className="row">
<Header />
</div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 导航区 */}
<NavLink className={computedClassName} to="/about">About</NavLink>
<NavLink className={computedClassName} to="/home">Home</NavLink>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 展示区 */}
{element}
</div>
</div>
</div>
</div>
</div>
)
}
——————————————记录于2022.05.27——————————————
6.2.5 嵌套路由
-
子路由添加到路由表中的children中
-
子路由使用< Outlet/>标签来注册路由
-
注意路径不用写父路径,直接写不用加 ' / '
//About.jsx
import React from 'react'
import { NavLink, Outlet } from 'react-router-dom'
export default function About() {
return (
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
<div>
<ul className="nav nav-tabs">
<li>
<NavLink className="list-group-item" to="news">News</NavLink>
</li>
<li>
<NavLink className="list-group-item" to="message">Message</NavLink>
</li>
</ul>
<Outlet />
</div>
</div>
</div>
</div>
)
}
//路由表
import { Navigate } from "react-router-dom"
import About from "../pages/About"
import Home from "../pages/Home"
import News from "../pages/About/News"
import Message from "../pages/About/Message"
const routes = [
{
path: '/about',
element: <About />,
children: [{
path: 'news',
element: <News />
}, {
path: 'message',
element: <Message />
}]
}, {
path: '/home',
element: <Home />
}, {
path: '*',
element: < Navigate to="/about" />
}
]
export default routes
6.2.6 路由参数
1. params参数
定义跳转规则及注册路由
import React, { useState } from 'react'
import { Link, Outlet } from 'react-router-dom'
export default function Message() {
const [message] = useState([
{
id: '001', title: 'msg1', content: 'hello_world'
}, {
id: '002', title: 'msg2', content: 'hello_China'
}, {
id: '003', title: 'msg3', content: 'hello_future'
}
])
return (
<div>
<ul>
{
message.map((msg) => {
return (
<li key={msg.id}>
<Link to={`detail/${msg.id}/${msg.title}/${msg.content}`}>{msg.title}</Link>
</li>
)
})
}
</ul>
<Outlet />
</div>
)
}
定义路由表
import { Navigate } from "react-router-dom"
import About from "../pages/About"
import Home from "../pages/Home"
import News from "../pages/About/News"
import Message from "../pages/About/Message"
import Detail from "../pages/About/Message/Detail"
const routes = [
{
path: '/about',
element: <About />,
children: [{
path: 'news',
element: <News />
}, {
path: 'message',
element: <Message />,
children: [{
path: 'detail/:id/:title/:content',
element: <Detail />
}]
}]
}, {
path: '/home',
element: <Home />
}, {
path: '*',
element: < Navigate to="/about" />
}
]
export default routes
接收参数
import React from 'react'
import { useParams } from 'react-router-dom';
export default function Detail() {
const { id, title, content } = useParams();
return (
<ul>
<li>{id}</li>
<li>{title}</li>
<li>{content}</li>
</ul>
)
}
2. search参数
发送:
< Link to={`detail?id=${msg.id}&title=${msg.title}&content=${msg.content}`}>{msg.title}< /Link>
路由表不用占位
接收:需要结构search参数,拿到search调用get方法用来接收
import React from 'react'
import { useSearchParams } from 'react-router-dom';
export default function Detail() {
const [search, setSearch] = useSearchParams()
return (
<ul>
<li>{search.get('id')}</li>
<li>{search.get('title')}</li>
<li>{search.get('content')}</li>
</ul>
)
}
3. state参数
指定跳转规则
<Link to="detail" state={{ id: msg.id, title: msg.title, content: msg.content }}>{msg.title}</Link>
路由表不用占位
useLocation接收路由参数
import React from 'react'
import { useLocation } from 'react-router-dom';
export default function Detail() {
const { state: { id, title, content } } = useLocation()
return (
<ul>
<li>{id}</li>
<li>{title}</li>
<li>{content}</li>
</ul>
)
}
6.2.7 编程式路由导航
import { Link, Outlet, useNavigate } from 'react-router-dom'
export default function Message() {
const navigate = useNavigate()
function showDetail(msg) {
navigate('detail', {
state: {
id: msg.id,
title: msg.title,
content: msg.content
}
})
}
return <button onClick={() => showDetail(msg)}>查看详情</button>
}
import React from 'react'
import { useNavigate } from 'react-router-dom'
export default function Header() {
const navigate = useNavigate()
function back() {
navigate(-1)
}
function forward() {
navigate(1)
}
return (
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header">
<h2>React Router Demo</h2>
<button onClick={back}>后退</button>
<button onClick={forward}>前进</button>
</div>
</div>
)
}
6.2.8 unseNavigation Type()
作用 :返回当前的导航类型(用户是如何来到当前页面的)
返回值:pop、push、replace
备注:pop是指在 浏览器中直接打开了这个路由组件(刷新页面)
6.2.9 useOutlet()
作用:用来呈现当前组件中药 渲染的嵌套路由
const result=useOutlet()
console.log(result)
6.2.10 useResolvedPath()
作用:给定一个url值,解析其中的path、search、hash值
——————————————本章笔记完成于2022.05.28——————————————
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步