什么是SPA?
就是一个项目里面只有一个html文件,无论是vue还是react项目,都是只有public文件夹里面的一个Index.html文件。为什么可以这样做,就能够有丰富的交互功能,页面跳转什么的都可以实现?因为有了路由技术。
在没有路由技术之前,跳转需要使用a标签来进行跳转,a标签的href指向的是不同的html文件,那么跳转到相应的html时会刷新页面来更新渲染的内容。这是路由技术实现之前的解决办法。
前端路由技术有两种实现方式:hash和history。参考:https://zhuanlan.zhihu.com/p/61694291,这篇文章将前端路由和后端路由讲解的很清楚,有时间一定要重点看一下,前后端的路由问题搞清楚了,那么MVVM、MVC、MTV模型都可以搞清楚了,https://zhuanlan.zhihu.com/p/426782949。我记得我刚开始学习flask和Django的时候,就是路由没有搞清楚,所以一直学习的很艰难,甚至现在学习Nodejs的时候,也是路由没有搞清楚,所以搭建http服务一直不理解,不理解就学习不下去,所以一定要先将后端路由搞清楚了,再来学习Nodejs。甚至我感觉搞清楚了之后,Java我都不想学习了,因为我想学Java的全部原因只是为了搭建服务端,而Java实在太庞大,投入产出比会非常小,不如把已有的前端、nodejs、Python真正搞定,真的就没有必要学习Java了。
hash路由原理:
HTML页面中通过锚点定位原理可进行无刷新跳转,触发后url地址中会多出# + 'XXX'的部分,同时在全局的window对象上触发hashChange事件,这样在页面锚点哈希改变为某个预设值的时候,通过代码触发对应的页面DOM改变,就可以实现基本的路由。下面用a标签的#写法讲解一下“锚点定位”是什么样子的,注意:只是说明锚点的运行方式,至于hash路由是不是这种方式实现的,我不知道。
xxxxxxxxxx1312<html lang="en">3<head>4<meta charset="UTF-8">5<meta name="viewport" content="width=device-width, initial-scale=1.0">6<title>Document</title>7</head>8<body>9<a href="#demo1">跳转到demo1</a>10<a href="#demo2">跳转到demo2</a>11<a href="#demo3">跳转到demo3</a>12</body>13</html>运行效果,注意看url栏的变化:
mdn的一些概念:https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/a
history路由原理:
HTML5的History API(就是BOM的history API)为浏览器的全局history对象增加的扩展方法。一般用来解决ajax请求无法通过回退按钮回到请求前状态的问题。
简单理解的路由工作原理:路由模块里面监测URL里面的路径path的变化,比如说从localhost:5000变为了localhost:5000/home,那么路由模块里面的前端路由器会根据映射关系 key:value 来展示相应的组件。
在 /react全家桶资料/06其他/前端路由的基石history.html 中,老师引入了一个名称为history.js的第三方库,注意:这个history.js里面的createBrowserHistory()方法只是对BOM的history api的一个封装,本质上还是使用的BOM的history api。还有一个createHashHistory()方法,是对hash路由的实现。在里面可以看到,路由的实现其实很简单。
1. 什么是路由?
2. 路由分类
后端路由:
1)理解: value是function, 用来处理客户端提交的请求。
2)注册路由:router.get(path, function(req, res))
3)工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
这里以老师提供的server.js举例说明:

1)浏览器端路由,value是component,用于展示页面内容。
2)注册路由: <Route path="/test" component={Test}>
3)工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件
为什么学习的是react-router-dom,而不是react-router呢?因为react-router有三种实现:1、web端,2、react-native端,3、anywhere端(任意哪一个端)。
现在主要就是学习web端,也就是react-router-dom这个库。
为什么不学习anywhere端的实现呢?因为没有必要,大而全反而不知道怎么用,要学就学针对性最强的端。
Route和Router的区别?
Route翻译为“路由”,Router翻译为“路由器”。这里只讲react中二者的区别,vue中可能有一些不同。
Route 是用于声明路由映射到应用程序的组件层。
Router我们可以把它看做是react路由的一个路由外层盒子,它里面的内容就是我们单页面应用的路由以及路由组件。
<BrowserRouter><HashRouter><Route><Redirect><Link><NavLink><Switch>1、安装:npm install react-router-dom@5,提示:老师讲解的时候,是版本5,所以需要这么安装。在后面讲解了版本6,到时候有一些用法会不同,都有笔记的。
2、使用react-router-dom的一个套路:拿到一个需求,1、看哪是导航区,哪是展示区(主要区分这两个区域,因为这里暂时把别的区域看成是固定的了,当然顶部栏也可能根据导航区的变化而变化,那么顶部栏同样可以看成是另一个展示区);2、导航区对应的展示组件就可以先创建出来,里面放静态的页面;3、由于是SPA项目,以这个案例来说明,其实顶部的标题栏、左侧的导航栏、左下侧的展示区都是在app.jsx中的,更精准的说应该都在index.html中,所以导航链接组件和展示区的组件是在同一个文件中的。(这一点和vue是完全不同的,概念要区分清楚,vue里面的路由其实都定义在router/index.js中,由api来自动区分,写法也完全不同。其实react的这种用法更加直观、更好理解。我知道这里我还没有说清楚,如果感到难以理解,就多看几遍视频,这一点必须理解,非常重要)
3、先写静态页面,可以参考 /react全家桶资料/04_静态页面/route_page1 里面的代码,about.html和home.html的差别是什么?你仔细看一下,1、a标签的active状态在哪个上面,哪个标签就高亮,URL栏里面的path显示哪个地址,右侧的展示区就展示对应的组件;2、展示区的内容不一样,那么需要展示的组件就放在这里(这种写法要理解清楚,可能刚开始不是那么容易从vue写法转过来的) 。那么就把about.html里面的代码粘贴到app.jsx,css文件放到public里面里面,并引入。
xxxxxxxxxx451// App.jsx23import React, { Component } from "react";45export default class App extends Component {6 render() {7 return (8 <div>9 <div class="row">10 <div class="col-xs-offset-2 col-xs-8">11 <div class="page-header">12 <h2>React Router Demo</h2>13 </div>14 </div>15 </div>16 <div class="row">17 <div class="col-xs-2 col-xs-offset-2">18 <div class="list-group">1920 {/* 这是导航区,路由链接就要写到这里 */}21 <a class="list-group-item active" href="./about.html">22 About23 </a>24 <a class="list-group-item" href="./home.html">25 Home26 </a>272829 </div>30 </div>31 <div class="col-xs-6">32 <div class="panel">33 <div class="panel-body">3435 {/* 这是展示区,具体展示的组件就要写到这里 */}36 <h3>我是About的内容</h3>3738 </div>39 </div>40 </div>41 </div>42 </div>43 );44 }45}4、编写展示组件的内容。这里的例子非常简单,但其实组件里面的内容可以非常复杂,还可以是路由的嵌套,后面都会讲的。
xxxxxxxxxx111// Home.jsx23import React, { Component } from 'react'45export default class Home extends Component {6 render() {7 return (8 <h3>我是Home的内容</h3>9 )10 }11}xxxxxxxxxx111// About.jsx23import React, { Component } from 'react'45export default class About extends Component {6 render() {7 return (8 <h3>我是About的内容</h3>9 )10 }11}5、在App.jsx中引入路由的各种方法和组件(案例中主要是引入了<Route>、<Link>、<BrowserRouter>组件)。
①编写路由链接:
xxxxxxxxxx521// App.jsx23import React, { Component } from "react";4import { Link,Route,BrowserRouter } from 'react-router-dom'56export default class App extends Component {7 render() {8 return (9 <div>10 <div className="row">11 <div className="col-xs-offset-2 col-xs-8">12 <div className="page-header">13 <h2>React Router Demo</h2>14 </div>15 </div>16 </div>17 <div className="row">18 <div className="col-xs-2 col-xs-offset-2">19 <div className="list-group">2021 {/* 这是导航区,路由链接就要写到这里 */}2223 {/* 原生html中,靠<a></a>跳转不同的页面 */}24 {/* <a className="list-group-item active" href="./about.html">25 About26 </a>27 <a className="list-group-item" href="./home.html">28 Home29 </a> */}3031 {/* 在react中,靠路由链接实现切换组件 */}32 {/* to属性改变path,指的是想要跳转的path,注意path的值不要写大写。Link就是一个组件标签,该有的属性都有的,className同样要写。 */}33 <Link className="list-group-item" to="/about">About</Link>34 <Link className="list-group-item" to="/home">Home</Link>3536 </div>37 </div>38 <div className="col-xs-6">39 <div className="panel">40 <div className="panel-body">4142 {/* 这是展示区,具体展示的组件就要写到这里 */}43 <h3>我是About的内容</h3>4445 </div>46 </div>47 </div>48 </div>49 </div>50 );51 }52}启动npm start,看一下效果:

报错了,提示必须在<Router>组件里面使用<Link>组件,这个报错其实不是很精细准确,错误提示里面的<Router>其实不能直接使用,react-router-dom提供了两种router组件,<BrowserRouter>和<HashRouter>,这里使用<BrowserRouter>。
xxxxxxxxxx41<BrowserRouter>2 <Link className="list-group-item" to="/about">About</Link>3 <Link className="list-group-item" to="/home">Home</Link>4</BrowserRouter>显示效果:

可以看到,点击路由链接的时候,path路径发生了变化。但是展示区没有变化,那么下一步就是编写展示区。
②编写展示区:
xxxxxxxxxx601// App.jsx23import React, { Component } from "react";4import { Link,Route,BrowserRouter } from 'react-router-dom'5import Home from './components/Home'6import About from './components/About'78export default class App extends Component {9 render() {10 return (11 <div>12 <div className="row">13 <div className="col-xs-offset-2 col-xs-8">14 <div className="page-header">15 <h2>React Router Demo</h2>16 </div>17 </div>18 </div>19 <div className="row">20 <div className="col-xs-2 col-xs-offset-2">21 <div className="list-group">2223 {/* 这是导航区,路由链接就要写到这里 */}2425 {/* 原生html中,靠<a></a>跳转不同的页面 */}26 {/* <a className="list-group-item active" href="./about.html">27 About28 </a>29 <a className="list-group-item" href="./home.html">30 Home31 </a> */}3233 {/* 在react中,靠路由链接实现切换组件-----编写路由链接 */}34 {/* to属性改变path,指的是想要跳转的path,注意path的值不要有大写字母。 */}35 <BrowserRouter>36 <Link className="list-group-item" to="/about">About</Link>37 <Link className="list-group-item" to="/home">Home</Link>38 </BrowserRouter>39 </div>40 </div>41 <div className="col-xs-6">42 <div className="panel">43 <div className="panel-body">4445 {/* 这是展示区,具体展示的组件就要写到这里 */}46 {/* <h3>我是About的内容</h3> */}4748 49 {/* 注册路由 */}50 {/* Route里面的两个参数也很好理解,path是路径,对应的组件component就直接写上去 */}51 <Route path="/about" component={About}/>52 <Route path="/home" component={Home}/>53 </div>54 </div>55 </div>56 </div>57 </div>58 );59 }60}看一下效果:

报错了,提示<Route>组件必须写在<Router>里面,改造一下:
xxxxxxxxxx41<BrowserRouter>2 <Route path="/about" component={About}/>3 <Route path="/home" component={Home}/>4</BrowserRouter>看一下效果,不报错了,但是点击路由链接,展示区是没有变化的。

原因是什么呢?原因是整个应用要用一个路由器来管理,如果有多套<BrowserRouter></BrowserRouter>,那么多套之间是没有数据沟通的。所以需要在项目的index.js用<BrowserRouter></BrowserRouter>包裹app组件。
xxxxxxxxxx221// index.js23import React from "react";4import ReactDOM from "react-dom/client";5import { BrowserRouter } from "react-router-dom";6import "./index.css";7import App from "./App";8import reportWebVitals from "./reportWebVitals";910const root = ReactDOM.createRoot(document.getElementById("root"));11root.render(12 <React.StrictMode>13 <BrowserRouter>14 <App />15 </BrowserRouter>16 </React.StrictMode>17);1819// If you want to start measuring performance in your app, pass a function20// to log results (for example: reportWebVitals(console.log))21// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals22reportWebVitals();查看效果:

已经实现基本的功能了。
react-router的官方examples:https://github.com/remix-run/react-router/tree/dev/examples,可以参考里面的写法。
提示:
在react脚手架项目中,使用 ctrl+/ 可以在jsx中快速注释代码。那为什么之前练习时没有这种效果呢?是因为react脚手架帮我们实现了这个功能。
区别一:一般组件的使用:<Home />,路由组件的使用<Route path="/home" component={Home} />。
区别二:工程化的规定,路由组件放在pages文件夹里面,一般组件放在components文件夹里面。
区别三:一般组件如果不给它传值的话,它的this.props是{},写组件时传递了什么,就接收到什么。但是路由组件不给它传值的话,它的this.props是{history:{xxx},location:{xxxxx},match:{xxxx},staticContext:{xxxx}},路由组件就可以用这些值来处理相应的事情。
将Link组件换为NavLink组件就可以了。
xxxxxxxxxx41import {NavLink} from "react-router-dom"23<NavLink className="list-group-item" to="/about">About</NavLink>4<NavLink className="list-group-item" to="/home">Home</NavLink>为什么呢?a标签的案例中,是使用active这个类名来实现的高亮效果,为什么NavLink里面没有加任何类名,就实现了高亮效果呢?因为NavLink里面的一个属性activeClassName的默认值就是active,这只是一种巧合而已。
那么如果想换高亮的样式,那么就要显式的指定高亮的类名。样式可能有权重问题,调整一下就行了。
xxxxxxxxxx21<NavLink activeClassName="demo-active" className="list-group-item" to="/about">About</NavLink>2<NavLink activeClassName="demo-active" className="list-group-item" to="/home">Home</NavLink>
在使用<NavLink></NavLink>的时候,里面有很多固定的属性,那么就可以封装一个NavLink组件,只把不同的属性传过去就行了。标签属性好传值,但是<MyNavLink to="/home">Home</MyNavLink>,里面的这个标题Home,怎么传递过去呢?当然可以这样做:<MyNavLink to="/home" title="Home"></MyNavLink>,用this.props接收就行了,但是这样不是最佳方案。
<MyNavLink to="/home">Home</MyNavLink>这里的Home叫做标签体内容,我们输出一下,看this.props接受到没有。
xxxxxxxxxx431// App.jsx23import React, { Component } from "react";4import { Route } from "react-router-dom";5import About from "./pages/About";6import Home from "./pages/Home";7import MyNavLink from "./components/MyNavLink";89export default class App extends Component {10 render() {11 return (12 <div>13 <div className="row">14 <div className="col-xs-offset-2 col-xs-8">15 <div className="page-header">16 <h2>React Router Demo</h2>17 </div>18 </div>19 </div>20 <div className="row">21 <div className="col-xs-2 col-xs-offset-2">22 <div className="list-group">23 24 <MyNavLink to="/about">About</MyNavLink>25 <MyNavLink to="/home">Home</MyNavLink>26 27 </div>28 </div>29 <div className="col-xs-6">30 <div className="panel">31 <div className="panel-body">32 33 <Route path="/about" component={About} />34 <Route path="/home" component={Home} />35 36 </div>37 </div>38 </div>39 </div>40 </div>41 );42 }43}xxxxxxxxxx141// MyNavLink.jsx23import React, { Component } from 'react'4import {NavLink} from 'react-router-dom'56export default class MyNavLink extends Component {7 render() {8 console.log(this.props)9 const {to,title} = this.props10 return (11 <NavLink activeClassName="active" className="list-group-item" to={to}>{title}</NavLink>12 )13 }14}
注意:这里输出了两次,还不知道具体原因是什么,等做项目的时候再看吧。
可以看到标签体也是一个特殊的标签属性,那么怎么接收呢?可以用this.props.children来接收。但是既然它是一个标签属性,那么直接写在标签里面行不行呢?是可以的。
xxxxxxxxxx141// MyNavLink.jsx23import React, { Component } from 'react'4import {NavLink} from 'react-router-dom'56export default class MyNavLink extends Component {7 render() {8 return (9 {/* 使用了 {...this.props} 的方法 */}10 <NavLink activeClassName="active" className="list-group-item" {this.props}></NavLink>11 )12 }13}14通常情况下,<Route />里面的path和component是一一对应的关系,但是有时候代码写错了或者特殊要求,写成这样了:
xxxxxxxxxx21<Route to="home" component={Home} />2<Route to="test" component={Test} />那么路由在匹配时,会将两个都匹配上,都展示出来。那么就要用到<Switch></Switch>组件。
Switch可以提高路由匹配效率(单一匹配,一旦匹配到就不再进行匹配操作。即使代码写的不像上面的错误代码那样,也应该使用Switch组件,因为如果路由很多的话,使用Switch一旦匹配到,就不会去找别的组件,提高了效率)。
xxxxxxxxxx431// App.jsx23import React, { Component } from "react";4import { Route, Switch } from "react-router-dom";5import About from "./pages/About";6import Home from "./pages/Home";7import MyNavLink from "./components/MyNavLink";89export default class App extends Component {10 render() {11 return (12 <div>13 <div className="row">14 <div className="col-xs-offset-2 col-xs-8">15 <div className="page-header">16 <h2>React Router Demo</h2>17 </div>18 </div>19 </div>20 <div className="row">21 <div className="col-xs-2 col-xs-offset-2">22 <div className="list-group">23 <MyNavLink to="/about">About</MyNavLink>24 <MyNavLink to="/home">Home</MyNavLink>25 </div>26 </div>27 <div className="col-xs-6">28 <div className="panel">29 <div className="panel-body">30 31 <Switch>32 <Route path="/about" component={About} />33 <Route path="/home" component={Home} />34 </Switch>35 36 </div>37 </div>38 </div>39 </div>40 </div>41 );42 }43}有一个需求,就是路由跳转的时候,path的地址要带上公司名称等等,写成这样<NavLink to="/atguigu/home" >Home</NavLink>,但这不是二级路由,只是规定了要带上/atguigu,会造成在刷新浏览器的时候,样式丢失。
原因是什么呢?原因是public/index.html引入CSS文件时,写成了这样:<link rel="stylesheet" href="./css/bootstrap.css" />,使用了相对路径。那么路由跳转的时候,路由地址是这样的http://localhost:3000/atguigu/home,点击浏览器刷新,webpack的devServer找资源实际上的处理是这样的:http://localhost:3000/atguigu/css/bootstrap.css,肯定找不到这个文件啊,找不到就默认返回public/index.html,所以没有样式。
为什么刚开始样式没有问题呢?因为刚开始的时候路由地址是这样的http://localhost:3000,肯定可以找到css文件。
解决办法有三种:
方法1:将引入css的路径写成绝对路径
xxxxxxxxxx11<link rel="stylesheet" href="/css/bootstrap.css">方法2:在引入css时,使用%PUBLIC_URL%
xxxxxxxxxx11<link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap.css">方法3:使用HashRouter,一般不推荐这种方法,因为大厂讲究美观,路由也要美观。
xxxxxxxxxx191// 项目的index.js文件23import React from "react";4import ReactDOM from "react-dom/client";5import { HashRouter } from "react-router-dom";6import "./index.css";7import App from "./App";8import reportWebVitals from "./reportWebVitals";910const root = ReactDOM.createRoot(document.getElementById("root"));11root.render(12 <React.StrictMode>13 <HashRouter>14 <App />15 </HashRouter>16 </React.StrictMode>17);1819reportWebVitals();1、<Route />默认使用的是模糊匹配,也就是“输入的路径”(<NavLink to="/home/a/b">Home</NavLink>)必须要包含“匹配的路径”(<Route to="/home" component={Home} />),且顺序要一致。
那此时匹配到了Home组件,但是输入的路径,后面的
/a/b肯定是有用的啊,像在vue中就是匹配具体的页面了。在react中应该怎么处理呢?Home组件里面肯定有子路由的,子路由的to属性就是具体的路径了,在下面的“嵌套路由”课程中可以看到。
比如说,如果“输入的路径”为to="/a/home/b",那么顺序不一致,也是匹配不了的。
2、开启严格匹配:<Route exact={true} path="/home" component={Home} />,可以简写为:<Route exact path="/home" component={Home} />。“严格匹配”就是指“输入的路径”全等于“匹配的路径”。
3、严格匹配不要随便开启,有需要再开,有时候开启会导致无法继续匹配二级路由。
需求:页面一打开,默认选中一个路由,右侧展示相应的组件。不要是空白的页面。
这时候就要用到<Redirect />组件了,一般写在所有注册路由的下方,当所有路由都无法匹配时,跳转到<Redirect />指定的路由。
xxxxxxxxxx91// App.jsx23import {Route,Switch,Redirect} from "react-router-dom"45<Switch>6 <Route path="/about" component={About} />7 <Route path="/home" component={Home} />8 <Redirect to="/about" />9</Switch>
可以看到,输入的地址是http://localhost:3000,立即跳转到http://localhost:3000/about了。
为什么能行呢?你想一想,我访问时输入的是http://localhost:3000这个地址,路由监测到的path是"",与任何注册路由都匹配不了,那么就找到<Redirect />,跳转到它指定的路由。

分析一下:
1、Home组件内容里面嵌套了一个“导航区+展示区”,那么只需要在Home组件里面进行相应的修改即可,里面创建两个组件News和Message,点击导航区就展示相应的组件。

2、编写静态页面
在 /react全家桶资料/04_静态页面/route_page2 中可以看到编写好的Home部分的代码,将这部分代码先粘贴到Home/index.jsx里面。
xxxxxxxxxx481// Home/index.jsx23import React, { Component } from "react";45export default class Home extends Component {6 render() {7 return (8 <div>9 <h2>Home</h2>10 <div>11 <ul className="nav nav-tabs">1213 {/* 导航区----编写路由链接 */}14 <li>15 <a className="list-group-item" href="./home-news.html">16 News17 </a>18 </li>19 <li>20 <a className="list-group-item active" href="./home-message.html">21 Message22 </a>23 </li>2425 </ul>2627 {/* 展示区----注册组件 */}28 <div>29 <ul>30 <li>31 <a href="/message1">message001</a> 32 </li>33 <li>34 <a href="/message2">message002</a> 35 </li>36 <li>37 <a href="/message/3">message003</a> 38 </li>39 </ul>40 </div>414243 </div>44 </div>45 );46 }47}483、编写路由组件的静态页面,将News和Message组件的静态页面编写好。
xxxxxxxxxx151// News.jsx23import React, { Component } from "react";45export default class News extends Component {6 render() {7 return (8 <ul>9 <li>news001</li>10 <li>news002</li>11 <li>news003</li>12 </ul>13 );14 }15}xxxxxxxxxx231// Message.jsx23import React, { Component } from "react";45export default class Message extends Component {6 render() {7 return (8 <div>9 <ul>10 <li>11 <a href="/message1">message001</a> 12 </li>13 <li>14 <a href="/message2">message002</a> 15 </li>16 <li>17 <a href="/message/3">message003</a> 18 </li>19 </ul>20 </div>21 );22 }23}4、在Home组件中编写路由链接,注册路由。
xxxxxxxxxx351// Home/index.jsx23import React, { Component } from "react";4import { Route, Redirect, Switch } from "react-router-dom";5import MyNavLink from "../../components/MyNavLink";6import News from "./News";7import Message from "./Message";89export default class Home extends Component {10 render() {11 return (12 <div>13 <h2>Home</h2>14 <div>15 <ul className="nav nav-tabs">16 {/* 导航区----编写路由链接 */}17 <li>18 <MyNavLink to="/news">News</MyNavLink>19 </li>20 <li>21 <MyNavLink to="/message">Message</MyNavLink>22 </li>23 </ul>2425 {/* 展示区----注册组件 */}26 <Switch>27 <Route to="/news" component={News} />28 <Route to="/message" component={Message} />29 <Redirect to="/news" />30 </Switch>31 </div>32 </div>33 );34 }35}看一下效果:

可以看到,点击Home里面的导航栏,路由跳转到/about了。为什么呢?
因为路由的匹配是有顺序的,路由首先匹配最外层的/about 和 /home,然后匹配Home里面的 /news 和 /message,当点击news或message时,路由还是先从最外层开始匹配,/about和/home都没有匹配上,那么就会执行<Redirect />里面的代码,于是就跳转到了 /about 了。
解决办法:
需要在编写路由链接和注册组件时,path加上父级的path(这里就涉及到了模糊匹配的知识了)。
xxxxxxxxxx351// Home/index.jsx23import React, { Component } from "react";4import { Route, Switch, Redirect } from "react-router-dom";5import MyNavLink from "../../components/MyNavLink";6import News from "./News";7import Message from "./Message";89export default class Home extends Component {10 render() {11 return (12 <div>13 <h2>Home</h2>14 <div>15 <ul className="nav nav-tabs">16 {/* 导航区----编写路由链接 */}17 <li>18 <MyNavLink to="/home/news">News</MyNavLink>19 </li>20 <li>21 <MyNavLink to="/home/message">Message</MyNavLink>22 </li>23 </ul>2425 {/* 展示区----注册组件 */}26 <Switch>27 <Route to="/home/news" component={News} />28 <Route to="/home/message" component={Message} />29 <Redirect to="/home/news" />30 </Switch>31 </div>32 </div>33 );34 }35}显示效果:

可以看到,Home里面的导航栏和组件都是正常的,但是设置的News默认高亮没有效果,<Redirect to="/home/news" />没有效果。暂时还不知道原因,也搜索不到答案。
知道是什么原因吗???原来我把<Route />里面的path属性写成了to属性,路由当然匹配不到了。改成这样就OK了。
xxxxxxxxxx91{/* 展示区----注册组件 */}2<Switch>3 {/*<Route to="/home/news" component={News} />4 <Route to="/home/message" component={Message} /> */ } 5 6 <Route path="/home/news" component={News} />7 <Route path="/home/message" component={Message} />8 <Redirect to="/home/news" />9</Switch>

分析一下:
1、Message组件里面嵌套了一个“导航区+展示区”,那么只需要在Message组件里面进行相应的修改即可,里面创建Detail组件,点击导航区就展示相应的组件。

2、编写静态页面。把导航区和展示区分出来。
xxxxxxxxxx291// Message/index.jsx23import React, { Component } from "react";45export default class Message extends Component {6 render() {7 return (8 <div>910 {/* 导航区-----编写路由链接 */}11 <ul>12 <li>13 <a href="/message1">message001</a> 14 </li>15 <li>16 <a href="/message2">message002</a> 17 </li>18 <li>19 <a href="/message/3">message003</a> 20 </li>21 </ul>2223 {/* 展示区-----注册路由 */}24 25 26 </div>27 );28 }29}3、编写路由组件的静态页面。静态页面就是ul和li,自己写一下。这个案例有一点不同,就是需要根据不同的message返回不同的详情信息,怎么办?就需要传递路由参数过来,根据这个参数做不同的处理。
怎么接收这个参数呢?在5.3.2讲解一般组件和路由组件的区别时,已经说明了,路由组件的this.props携带有参数,可以用这里面的参数。
xxxxxxxxxx371// Message/Detail/index.jsx23import React, { Component } from "react";45// 这里定义了一些固定数据,用来模拟展示不同的信息。实际项目中,是根据传递过来的不同参数,做网络请求来获取不同的详情信息。6const DetailMessage = [7 {8 id: "1",9 content: "你好,Lily",10 },11 {12 id: "2",13 content: "你好,Tom",14 },15 {16 id: "3",17 content: "你好,Jerry",18 },19];2021export default class Detail extends Component {22 render() {23 // 可以输出this.props来看一下具体的参数24 // 接收params参数25 const { id, title } = this.props.match.params;26 const findResult = DetailMessage.find(detailObj => {27 return detailObj.id === id28 })29 return (30 <ul>31 <li>ID:{id}</li>32 <li>TITLE:{title}</li>33 <li>CONTENT:{findResult.content}</li>34 </ul>35 );36 }37}4、编写路由链接,注册路由。既然需求是不同的message展示不同的详情,那么在跳转到详情界面时,就要传递路由参数过去,怎么传递呢?
这里以传递params参数作说明,套路是固定的,按套路来写就行了:
xxxxxxxxxx491// Message/Detail.jsx23import React, { Component } from "react";4import { Route, Link } from "react-router-dom";5import MyNavLink from "../../../components/MyNavLink";6import Detail from "./Detail";78export default class Message extends Component {9 state = {10 messageArr: [11 {12 id: "1",13 title: "message001",14 },15 {16 id: "2",17 title: "message002",18 },19 {20 id: "3",21 title: "message003",22 },23 ],24 };2526 render() {27 const { messageArr } = this.state;28 return (29 <div>30 {/* 导航区-----编写路由链接 */}31 <ul>32 {messageArr.map((msgObj) => (33 <li>34 {/* 这里的path地址,一定要带上之前的层级,否则是不会匹配成功的。 */}35 {/* 向路由组件传递params参数,路由链接要携带参数,只需要写到{}中即可,里面使用模板字符串携带动态参数。只需要注意写法即可,这种都是套路,固定的。 */}36 <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>37 </li>38 ))}39 </ul>4041 <hr />4243 {/* 展示区-----注册路由。注意:由于详情的组件是通用的,所以只注册了一个路由,就不需要使用Switch和Redirect了。 */}44 {/* 声明接收params参数。这里规定了接收的参数key是什么,那么接收的时候就需要用这些key来取值。 */}45 <Route path="/home/message/detail/:id/:title" component={Detail} />46 </div>47 );48 }49}看一下效果:

xxxxxxxxxx491// Message/Detail.jsx23import React, { Component } from "react";4import { Route, Link } from "react-router-dom";5import MyNavLink from "../../../components/MyNavLink";6import Detail from "./Detail";78export default class Message extends Component {9 state = {10 messageArr: [11 {12 id: "1",13 title: "message001",14 },15 {16 id: "2",17 title: "message002",18 },19 {20 id: "3",21 title: "message003",22 },23 ],24 };2526 render() {27 const { messageArr } = this.state;28 return (29 <div>30 {/* 导航区-----编写路由链接 */}31 <ul>32 {messageArr.map((msgObj) => (33 <li key={msgObj.id}>34 {/* 这里的path地址,一定要带上之前的层级,否则是不会匹配成功的。 */}35 {/* 向路由组件传递params参数。只需要注意写法即可,这种都是套路,固定的。 */}36 <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>37 </li>38 ))}39 </ul>4041 <hr />4243 {/* 展示区-----注册路由。注意:由于详情的组件是通用的,所以只注册了一个路由,就不需要使用Switch和Redirect了。 */}44 {/* 声明接收params参数。这里规定了接收的参数key是什么,那么接收的时候就需要用这些key来取值。 */}45 <Route path="/home/message/detail/:id/:title" component={Detail} />46 </div>47 );48 }49}1、路由链接(携带参数)
xxxxxxxxxx11<Link to={`/home/message/detail/${id}/${name}`}>详情</Link>携带的其实是“value”。
2、注册路由(声明接收)
xxxxxxxxxx11<Route path="/home/message/detail/:id/:name" component={Detail} />声明接收的其实是“key”。
3、接收参数
在接收的时候,是从一个对象接收的,通过上面的方式已经定义好了。
const {id,name} = this.props.match.params
传递参数:
xxxxxxxxxx531// Message/index.jsx23import React, { Component } from "react";4import { Route, Link } from "react-router-dom";5import MyNavLink from "../../../components/MyNavLink";6import Detail from "./Detail";78export default class Message extends Component {9 state = {10 messageArr: [11 {12 id: "1",13 title: "message001",14 },15 {16 id: "2",17 title: "message002",18 },19 {20 id: "3",21 title: "message003",22 },23 ],24 };2526 render() {27 const { messageArr } = this.state;28 return (29 <div>30 {/* 导航区-----编写路由链接 */}31 <ul>32 {messageArr.map((msgObj) => (33 <li key={msgObj.id}>34 {/* 这里的path地址,一定要带上之前的层级,否则是不会匹配成功的。 */}35 36 {/* 向路由组件传递search参数。 */}37 <Link to={`/home/message/detail?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>38 </li>39 ))}40 </ul>4142 <hr />4344 {/* 展示区-----注册路由。注意:由于详情的组件是通用的,所以只注册了一个路由,就不需要使用Switch和Redirect了。 */}45 46 {/* search参数无需声明接收,正常注册路由即可 */}47 <Route path="/home/message/detail" component={Detail} />48 49 </div>50 );51 }52}53接收参数:
xxxxxxxxxx401// Message/Detail/index.jsx23import React, { Component } from "react";45// querystring是nodejs自带的,不需要另外安装。里面有两个方法很重要,qs.stringify()和qs.parse()。qs.stringify让一个对象转成为urlencoded的字符串。qs.parse()让一个urlencoded的字符串转成一个对象。6import qs from "querystring";78// 这里定义了一些固定数据,用来模拟展示不同的信息。实际项目中,是根据传递过来的不同参数,做网络请求来获取不同的详情信息。9const DetailMessage = [10 {11 id: "1",12 content: "你好,Lily",13 },14 {15 id: "2",16 content: "你好,Tom",17 },18 {19 id: "3",20 content: "你好,Jerry",21 },22];2324export default class Detail extends Component {25 render() {26 // 接收search参数27 const { id, title } = qs.parse(this.props.location.search.slice(1));2829 const findResult = DetailMessage.find((detailObj) => {30 return detailObj.id === id;31 });32 return (33 <ul>34 <li>ID:{id}</li>35 <li>TITLE:{title}</li>36 <li>CONTENT:{findResult.content}</li>37 </ul>38 );39 }40}看一下效果:

原因:querystring原本是Node.js自带的库,再新版本中已被弃用,在名称上有所调整,功能基本没有太大变化,解决方案如下。
import qs from "qs"
看一下效果:

1、路由链接(携带参数)
xxxxxxxxxx11<Link to={`/home/message/detail?id=${id}&title=${title}`}>详情</Link>2、注册路由(无需声明接收,正常注册即可)
xxxxxxxxxx11<Route path="/home/message/detail" component={Detail} />3、接收参数
xxxxxxxxxx51// 接收到的search是urlencoded编码字符串,需要借助qs解析2import qs from "qs"34const {search} = this.props.location5const {id,title} = qs.parse(search.slice(1))
注意:这个state参数是路由组件上to属性里面的state参数,不要和组件上的state状态搞混了。
xxxxxxxxxx511// Message/index.jsx23import React, { Component } from "react";4import { Route, Link } from "react-router-dom";5import MyNavLink from "../../../components/MyNavLink";6import Detail from "./Detail";78export default class Message extends Component {9 state = {10 messageArr: [11 {12 id: "1",13 title: "message001",14 },15 {16 id: "2",17 title: "message002",18 },19 {20 id: "3",21 title: "message003",22 },23 ],24 };2526 render() {27 const { messageArr } = this.state;28 return (29 <div>30 {/* 导航区-----编写路由链接 */}31 <ul>32 {messageArr.map((msgObj) => (33 <li key={msgObj.id}>34 {/* 这里的path地址,一定要带上之前的层级,否则是不会匹配成功的。 */}3536 {/* 向路由组件传递state参数,to的属性值是一个对象。pathname指定path路径,state传递参数。 */}37 <Link to={{pathname:"/home/message/detail",state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>38 </li>39 ))}40 </ul>4142 <hr />4344 {/* 展示区-----注册路由。注意:由于详情的组件是通用的,所以只注册了一个路由,就不需要使用Switch和Redirect了。 */}4546 {/* state参数无需声明接收,正常注册路由即可 */}47 <Route path="/home/message/detail" component={Detail} />48 </div>49 );50 }51}xxxxxxxxxx371// Message/Detail.jsx23import React, { Component } from "react";45// 这里定义了一些固定数据,用来模拟展示不同的信息。实际项目中,是根据传递过来的不同参数,做网络请求来获取不同的详情信息。6const DetailMessage = [7 {8 id: "1",9 content: "你好,Lily",10 },11 {12 id: "2",13 content: "你好,Tom",14 },15 {16 id: "3",17 content: "你好,Jerry",18 },19];2021export default class Detail extends Component {22 render() {23 // 接收state参数24 const {id,title} = this.props.location.state25 const findResult = DetailMessage.find((detailObj) => {26 return detailObj.id === id;27 });28 return (29 <ul>30 <li>ID:{id}</li>31 <li>TITLE:{title}</li>32 <li>CONTENT:{findResult.content}</li>33 </ul>34 );35 }36}371、路由链接(携带参数)
xxxxxxxxxx11<Link to={{pathname:"`/home/message/detail",state:{id:id,title:title}}}>详情</Link>2、注册路由(无需声明接收,正常注册即可)
xxxxxxxxxx11<Route path="/home/message/detail" component={Detail} />3、接收参数
xxxxxxxxxx11const {id,title} = this.props.location.state
1、三种传参方式,在浏览器刷新后都可以保留住状态。(这是在BrowserRouter情况下说的,BrowserRouter实际上使用的是浏览器的history api,浏览器会保留住状态。但是HashRouter在刷新后会导致state参数丢失。)
2、三种传参方式的使用都很多,都要掌握,如果非要分一下,那么params传参可能是用的最多的。

浏览器历史记录(路由记录)实际上是栈结构,push操作是将路由链接压栈;replace操作是替换掉目前的路由链接,也就是栈顶元素。
在编写路由链接的时候,如果不指定路由跳转方式,那么就默认是push方式。如何开启replace模式呢?需要添加replace属性。
xxxxxxxxxx51{/* 完整写法 */}2<Link replace={true} to={{pathname:"/home/message/detail",state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>34{/* 简写 */}5<Link replace to={{pathname:"/home/message/detail",state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>需求:
1、案例中,展示news组件之后,2秒后自动切换到Message组件。
2、在Message的li后面加上两个按钮,一个通过push查看详情,一个通过replace查看详情。
3、下方添加“返回”和“前进”按钮,replace查看详情时是什么效果,push查看详情时是什么效果,自己看一下。
xxxxxxxxxx201// News/index.jsx23import React, { Component } from "react";45export default class News extends Component {6 componentDidMount(){7 setTimeout(() => {8 this.props.history.push(`/home/message`)9 }, 2000);10 }11 render() {12 return (13 <ul>14 <li>news001</li>15 <li>news002</li>16 <li>news003</li>17 </ul>18 );19 }20}
xxxxxxxxxx1021// Message/index.jsx23import React, { Component } from "react";4import { Route, Link } from "react-router-dom";5import MyNavLink from "../../../components/MyNavLink";6import Detail from "./Detail";78export default class Message extends Component {9 state = {10 messageArr: [11 {12 id: "1",13 title: "message001",14 },15 {16 id: "2",17 title: "message002",18 },19 {20 id: "3",21 title: "message003",22 },23 ],24 };2526 replaceShow = (id, title) => {27 // 注意:这里的this.props为什么会有history属性???因为这个Message/index.jsx被作为一个路由组件在使用。注册在Home/index.jsx里面。这一点一定要搞清楚,不是什么组件都会有this.props.history的。2829 // 注意:相应的携带参数方式,对应的<Link>、<NavLink>编写路由链接和<Route />注册路由的方式都要进行相应修改。并且接收参数的组件里面,接收的方式也要进行修改。30 // replace跳转+携带params参数31 this.props.history.replace(`/home/message/detail/${id}/${title}`);3233 // replace跳转+携带search参数34 // this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`);3536 // replace跳转+携带state参数37 // this.props.history.replace(`/home/message/detail`, { id, title });38 };3940 pushShow = (id, title) => {41 // 注意:这里的this.props为什么会有history属性???因为这个Message/index.jsx本身是一个路由组件。注册在Home/index.jsx里面。这一点一定要搞清楚,不是什么组件都会有this.props.history的。4243 // 注意:相应的携带参数方式,对应的<Link>、<NavLink>编写路由链接和<Route />注册路由的方式都要进行相应修改。并且接收参数的组件里面,接收的方式也要进行修改。44 // push跳转+携带params参数45 this.props.history.push(`/home/message/detail/${id}/${title}`);4647 // push跳转+携带search参数48 // this.props.history.push(`/home/message/detail?id=${id}&title=${title}`);4950 // push跳转+携带state参数51 // this.props.history.push(`/home/message/detail`, { id, title });52 };5354 back = () => {55 this.props.history.goBack();56 };5758 forward = () => {59 this.props.history.goForward();60 };6162 go = () => {63 // go方法里面的参数,如果是正数,就是路由记录前进;如果是负数,就是路由记录后退。64 this.props.history.go(-1);65 };6667 render() {68 const { messageArr } = this.state;69 return (70 <div>71 {/* 导航区-----编写路由链接 */}72 <ul>73 {messageArr.map((msgObj) => (74 <li key={msgObj.id}>75 {/* 这里的path地址,一定要带上之前的层级,否则是不会匹配成功的。 */}76 {/* 向路由组件传递params参数。只需要注意写法即可,这种都是套路,固定的。 */}77 <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>78 <button onClick={() => this.pushShow(msgObj.id, msgObj.title)}>push查看</button>79 <button onClick={() => this.replaceShow(msgObj.id, msgObj.title)}>replace查看</button>80 {/* 向路由组件传递search参数 */}81 {/* <Link to={`/home/message/detail?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link> */}82 {/* 向路由组件传递state参数 */}83 {/* <Link replace={true} to={{pathname:"/home/message/detail",state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link> */}84 </li>85 ))}86 </ul>87 <hr />88 {/* 展示区-----注册路由。注意:由于详情的组件是通用的,所以只注册了一个路由,就不需要使用Switch和Redirect了。 */}89 {/* 声明接收params参数。这里规定了接收的参数key是什么,那么接收的时候就需要用这些key来取值。 */}90 <Route path="/home/message/detail/:id/:title" component={Detail} />91 {/* search参数无需声明接收,正常注册路由即可 */}92 {/* <Route path="/home/message/detail" component={Detail} /> */}93 {/* state参数无需声明接收,正常注册路由即可 */}94 {/* <Route path="/home/message/detail" component={Detail} /> */}9596 <button onClick={this.back}>回退</button> 97 <button onClick={this.forward}>前进</button> 98 <button onClick={this.go}>go方法</button>99 </div>100 );101 }102}显示效果:

主要用到了this.props.history属性上的go,goForward,goBack,push,replace这5个api。
需求:
想把上一个案例中的“回退”和“前进”按钮放到Header组件里面,就是“React Router Demo”这里面,要求可以操作路由的回退和前进。
分析一下:
Header组件是一般组件,不是路由组件,所以this.props上是没有history属性的,那就没有history的api来操作路由了,怎么办?
解决办法:
react-router-dom有一个withRouter方法,方法的作用:接受一个一般组件,让一般组件具备路由组件所特有的api,然后返回一个新组件。
xxxxxxxxxx321// Header/index.jsx23import React, { Component } from "react";4import { withRouter } from 'react-router-dom'56class Header extends Component {7 back = () => {8 this.props.history.goBack();9 };1011 forward = () => {12 this.props.history.goForward();13 };1415 go = () => {16 // go方法里面的参数,如果是正数,就是路由记录前进;如果是负数,就是路由记录后退。17 this.props.history.go(-1);18 };1920 render() {21 return (22 <div className="page-header">23 <h2>React Router Demo</h2>24 <button onClick={this.back}>回退</button> 25 <button onClick={this.forward}>前进</button> 26 <button onClick={this.go}>go方法</button>27 </div>28 );29 }30}3132export default withRouter(Header)显示效果:

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可以用于解决一些路径错误相关的问题。(eg:样式丢失的问题)
安装:npm install antd
xxxxxxxxxx301import React, { Component } from 'react'2// 引入组件3import { Button,DatePicker } from 'antd';4// 引入样式5import 'antd/dist/antd.css'6// 引入图标7import {WechatOutlined,WeiboOutlined,SearchOutlined} from '@ant-design/icons'8const { RangePicker } = DatePicker;910export default class App extends Component {11 render() {12 return (13 <div>14 App....15 <button>点我</button>16 <Button type="primary">按钮1</Button>17 <Button >按钮2</Button>18 <Button type="link">按钮3</Button>19 <Button type="primary" icon={<SearchOutlined />}>20 Search21 </Button>22 <WechatOutlined />23 <WeiboOutlined />24 <DatePicker/>25 <RangePicker/>26 </div>27 )28 }29}30任何与antd用法相关的问题,首先查找文档,查找不到再去搜索。
官网地址:
https://ant-design.gitee.io/docs/react/use-with-create-react-app-cn
提示:
有些问题可能在最新版本的文档中找不到答案,这一点我在使用vant文档时也发现了。可以切换文档的版本,看一下有没有答案。
下面的方式是按照antd@3.x版本来做的,现在都antd@5.8.3了,这种方法能不能使用,还需要在真正使用的时候去搜索一下。
当然,如果最新版本的UI库一些问题就是没有办法解决,那就降低版本看能不能使用,这些都是技巧啊。




React Router 以三个不同的包发布到 npm 上,它们分别为:
<BrowserRouter>等 。<NativeRouter>等。与React Router 5.x 版本相比,改变了什么?
内置组件的变化:移除<Switch/> ,新增 <Routes/>等。
语法的变化:component={About} 变为 element={<About/>}等。
新增多个hook:useParams、useNavigate、useMatch等。
官方明确推荐函数式组件了!!!
......
以之前的路由案例为例,使用react-router6来重新做,顺便学习react-router6的用法。
1、先从 /react全家桶资料/04_静态页面/route_page1 和 route_page2 把静态页面拷贝过来。采用函数式组件来编写。
主要用到了
{element},useRoutes,定义routes,<Navigate />重定向,还有NavLink的指定样式,函数式组件props使用方法。
xxxxxxxxxx371// App.js23import React from 'react'4import { useRoutes } from 'react-router-dom'5import routes from "./routes"6import MyNavLink from './components/MyNavLink'7import Header from './components/Header'89export default function App() {10 const element = useRoutes(routes);11 return (12 <div>13 <div className="row">14 <Header />15 </div>16 <div className="row">17 <div className="col-xs-2 col-xs-offset-2">18 <div className="list-group">19 20 <MyNavLink to="/about">About</MyNavLink>21 <MyNavLink to="/home">Home</MyNavLink>22 23 </div>24 </div>25 <div className="col-xs-6">26 <div className="panel">27 <div className="panel-body">28 29 {element}30 31 </div>32 </div>33 </div>34 </div>35 </div>36 )37}xxxxxxxxxx231// /src/routes/index.js23import Home from '../pages/Home'4import About from '../pages/About'5import { Navigate } from 'react-router-dom'67const routes = [8 {9 path:"/about",10 element:<About />11 },12 {13 path:"/home",14 element: <Home />15 },16 // 重定向17 {18 path:"/",19 element: <Navigate to="/about" />20 }21]2223export default routesxxxxxxxxxx81// MyNavLink/index.js23import React from "react";4import { NavLink } from "react-router-dom";56export default function MyNavLink(props) {7 return <NavLink className={({ isActive }) => (isActive ? "list-group-item atguigu" : "list-group-item")} {props}></NavLink>;8}2、首先实现about和home的路由。
xxxxxxxxxx111// Home/index.js23import React from "react";45export default function Home() {6 return (7 <div>8 <h2>Home</h2>9 </div>10 );11}xxxxxxxxxx111// About/index.js23import React from "react";45export default function About() {6 return (7 <div>8 <h2>About</h2>9 </div>10 );11}显示效果OK。

3、实现news和message的路由。
主要用到了
<Outlet />,二级路由:children属性。
xxxxxxxxxx391// /src/routes/index.js23import Home from "../pages/Home";4import About from "../pages/About";5import Message from "../pages/Home/Message";6import News from "../pages/Home/News";7import { Navigate } from "react-router-dom";89const routes = [10 {11 path: "/about",12 element: <About />,13 },14 {15 path: "/home",16 element: <Home />,17 // 用到了children属性18 children: [19 {20 path: "news",21 element: <News />,22 },23 {24 path: "message",25 element: <Message />,26 },27 {28 index: true,29 element: <Navigate to="news" />,30 },31 ],32 },33 {34 path: "/",35 element: <Navigate to="/about" />,36 },37];3839export default routes;xxxxxxxxxx271// Home/index.js23import React from "react";4import MyNavLink from "../../components/MyNavLink";5import {Outlet} from 'react-router-dom'67export default function Home() {8 return (9 <div>10 <h2>Home</h2>11 <div>12 <ul className="nav nav-tabs">13 <li>14 <MyNavLink to="news">News</MyNavLink>15 </li>16 <li>17 <MyNavLink to="message">Message</MyNavLink>18 </li>19 </ul>20 <hr />21 22 <Outlet />23 24 </div>25 </div>26 );27}xxxxxxxxxx91// Home/News/index.js23import React from 'react'45export default function News() {6 return (7 <div>News</div>8 )9}xxxxxxxxxx91// Home/Message/index.js23import React from 'react'45export default function Message() {6 return (7 <div>Message</div>8 )9}4、实现detail的路由并传递路由参数。
主要用到了三种传递路由参数的方法,params,search,state。二级路由:children属性,
<Outlet />。接收三种参数的方法:useParams(),useSearchParams(),useLocation()。
xxxxxxxxxx581// /src/routes/index.js23import Home from "../pages/Home";4import About from "../pages/About";5import Message from "../pages/Home/Message";6import News from "../pages/Home/News";7import Detail from '../pages/Home/Message/Detail'8import { Navigate } from "react-router-dom";910const routes = [11 {12 path: "/about",13 element: <About />,14 },15 {16 path: "/home",17 element: <Home />,18 children: [19 {20 path: "news",21 element: <News />,22 },23 {24 path: "message",25 element: <Message />,26 children:[27 // params传递路由参数,注册路由需要声明。28 // {29 // path:`detail/:id/:title/:content`,30 // element: <Detail />31 // },3233 // search传递路由参数,注册路由不需要声明。34 // {35 // path:`detail`,36 // element: <Detail />37 // },3839 // state传递路由参数,注册路由不需要声明。40 {41 path:`detail`,42 element: <Detail />43 }44 ]45 },46 {47 index: true,48 element: <Navigate to="news" />,49 },50 ],51 },52 {53 path: "/",54 element: <Navigate to="/about" />,55 },56];5758export default routes;xxxxxxxxxx501// Message/index.js23import React, { useState } from "react";4import { Link, Outlet } from "react-router-dom";56export default function Message() {7 const [messageArr] = useState([8 { id: 1, title: "message001", content: "一枝秾艳露凝香" },9 { id: 2, title: "message002", content: "云雨巫山枉断肠" },10 { id: 3, title: "message003", content: "借问汉宫谁得似" },11 { id: 4, title: "message004", content: "可怜飞燕倚新妆" },12 { id: 5, title: "message005", content: "名花倾国两相欢" },13 { id: 6, title: "message006", content: "长得君王带笑看" },14 { id: 7, title: "message007", content: "解释春风无限恨" },15 { id: 8, title: "message008", content: "沉香亭北倚阑干" },16 ]);17 return (18 <div>19 <ul>20 {messageArr.map((m) => {21 return (22 <li key={m.id}>23 {/* params传递路由参数 */}24 {/* <Link to={`detail/${m.id}/${m.title}/${m.content}`}>{m.title}</Link> */}2526 {/* search传递路由参数 */}27 {/* <Link to={`detail?id=${m.id}&title=${m.title}&content=${m.content}`}>{m.title}</Link> */}2829 {/* state传递路由参数 */}30 <Link31 to="detail"32 state={{33 id: m.id,34 title: m.title,35 content: m.content,36 }}37 >38 {m.title}39 </Link>40 </li>41 );42 })}43 </ul>44 <hr />45 46 <Outlet />47 48 </div>49 );50}xxxxxxxxxx271// Message/Detail/index.js23import React from "react";4import { useParams, useSearchParams, useLocation } from "react-router-dom";56export default function Detail() {7 // params接收参数8 // const { id, title, content } = useParams();910 // search接收参数11 // const [search] = useSearchParams();12 // const id = search.get("id")13 // const title = search.get("title")14 // const content = search.get("content")1516 // state接收参数17 const {18 state: { id, title, content },19 } = useLocation();20 return (21 <ul>22 <li>ID:{id}</li>23 <li>TITLE:{title}</li>24 <li>CONTENT:{content}</li>25 </ul>26 );27}5、一般组件操作路由
主要用到了
useNavigate()方法。
xxxxxxxxxx381// Header/index.js23import React from "react";4import { useNavigate } from 'react-router-dom'56export default function Header() {7 const navigate = useNavigate();89 function back(){10 navigate(-1)11 }1213 function forward(){14 navigate(1)15 }1617 function go(){18 navigate("/home/message/detail",{19 replace:false,20 state:{21 id:9,22 title:"message009",23 content:"云想衣裳花想容"24 }25 })26 }2728 return (29 <div className="col-xs-offset-2 col-xs-8">30 <div className="page-header">31 <h2>React Router Demo</h2>32 <button onClick={back}>←后退</button>33 <button onClick={forward}>前进→</button>34 <button onClick={go}>go</button>35 </div>36 </div>37 );38}
<BrowserRouter>说明:<BrowserRouter>用于包裹整个应用。
示例代码:
xxxxxxxxxx101import React from "react";2import ReactDOM from "react-dom";3import { BrowserRouter } from "react-router-dom";45ReactDOM.render(6 <BrowserRouter>7 {/* 整体结构(通常为App组件) */}8 </BrowserRouter>9 ,root10);<HashRouter><BrowserRouter>一样,但<HashRouter>修改的是地址栏的hash值。<HashRouter>、<BrowserRouter> 的用法与 5.x 相同。<Routes/> 与 <Route/>v6版本中移除了先前的<Switch>,引入了新的替代者:<Routes>。
<Routes> 和 <Route>要配合使用,且必须要用<Routes>包裹<Route>。
<Route> 相当于一个 if 语句,如果其路径与当前 URL 匹配,则呈现其对应的组件。
<Route caseSensitive> ,caseSensitive属性用于指定:匹配时是否区分大小写(默认为 false)。
当URL发生变化时,<Routes>都会查看其所有子<Route> 元素以找到最佳匹配并呈现组件 。
<Route> 也可以嵌套使用,且可配合useRoutes()配置 “路由表” ,但需要通过 <Outlet> 组件来渲染其子路由。
示例代码:
xxxxxxxxxx161<Routes>2 /*path属性用于定义路径,element属性用于定义当前路径所对应的组件*/3 <Route path="/login" element={<Login />}></Route>45 /*用于定义嵌套路由,home是一级路由,对应的路径/home*/6 <Route path="home" element={<Home />}>7 /*test1 和 test2 是二级路由,对应的路径是/home/test1 或 /home/test2*/8 <Route path="test1" element={<Test/>}></Route>9 <Route path="test2" element={<Test2/>}></Route>10 </Route>11 12 //Route也可以不写element属性, 这时就是用于展示嵌套的路由 .所对应的路径是/users/xxx13 <Route path="users">14 <Route path="xxx" element={<Demo />} />15 </Route>16</Routes>
<Route path="/login" element={<Login />}></Route>element里面写成了标签,此时能不能传递props参数呢?这样操作起来就非常方便了。先查一下文档,试一下。我问了一下chatgpt,答案是可以,以后可以在实际项目中试一下。
通过
react-router-dom@6的element传递props的方法是可行的,你可以直接传递props给组件。例如:xxxxxxxxxx11<Route path="/login" element={<Login someProp={someValue} />} />然后在
Login组件中,可以直接通过props获取someProp的值:xxxxxxxxxx81function Login({ someProp }) {2return (3<div>4<h1>Login Page</h1>5<p>Prop value: {someProp}</p>6</div>7);8}在这个例子中,
Login组件会直接接收到someProp的值someValue。
<Link>作用: 修改URL,且不发送网络请求(路由链接)。
注意: 外侧需要用<BrowserRouter>或<HashRouter>包裹。
示例代码:
xxxxxxxxxx91import { Link } from "react-router-dom";23function Test() {4 return (5 <div>6 <Link to="/路径">按钮</Link>7 </div>8 );9}<NavLink>作用: 与<Link>组件类似,且可实现导航的“高亮”效果。
示例代码:
xxxxxxxxxx161// 注意: NavLink默认类名是active,下面是指定自定义的class23//自定义样式4<NavLink5 to="login"6 className={({ isActive }) => {7 console.log('home', isActive)8 return isActive ? 'base one' : 'base'9 }}10>login</NavLink>1112/*13 默认情况下,当Home的子组件匹配成功,Home的导航也会高亮,14 当NavLink上添加了end属性后,若Home的子组件匹配成功,则Home的导航没有高亮效果。15*/16<NavLink to="home" end >home</NavLink><Navigate>作用:只要<Navigate>组件被渲染,就会修改路径,切换视图。
replace属性用于控制跳转模式(push 或 replace,默认是push)。
示例代码:
xxxxxxxxxx141import React,{useState} from 'react'2import {Navigate} from 'react-router-dom'34export default function Home() {5 const [sum,setSum] = useState(1)6 return (7 <div>8 <h3>我是Home的内容</h3>9 {/* 根据sum的值决定是否切换视图 */}10 {sum === 1 ? <h4>sum的值为{sum}</h4> : <Navigate to="/about" replace={true}/>}11 <button onClick={()=>setSum(2)}>点我将sum变为2</button>12 </div>13 )14}<Outlet>当<Route>产生嵌套时,渲染其对应的后续子路由。
示例代码:
xxxxxxxxxx481//根据路由表生成对应的路由规则2const element = useRoutes([3 {4 path:'/about',5 element:<About/>6 },7 {8 path:'/home',9 element:<Home/>,10 children:[11 {12 // 注意:path里面不需要像react-router@5.x那样带上父级路由前缀了,并且不要写 / 前缀,因为router6会帮助处理的。13 path:'news',14 element:<News/>15 },16 {17 path:'message',18 element:<Message/>,19 }20 ]21 }22])2324//Home.js25import React from 'react'26import {NavLink,Outlet} from 'react-router-dom'2728export default function Home() {29 return (30 <div>31 <h2>Home组件内容</h2>32 <div>33 <ul className="nav nav-tabs">34 <li>35 // 注意:to属性里面不要像router5那样带上父级路由的前缀,并且不要写 / 前缀,否则会报错。36 <NavLink className="list-group-item" to="news">News</NavLink>37 </li>38 <li>39 <NavLink className="list-group-item" to="message">Message</NavLink>40 </li>41 </ul>42 {/* 指定路由组件呈现的位置 */}43 <Outlet />44 </div>45 </div>46 )47}48使用了useRoutes生成路由,并使用
{element}的方式将路由放到App.jsx里面,并不表示路由的操作就全部完成了。{element}只能渲染出路由表里面的第一级路由,像login、register、main、error这些页面可以当作一级路由来写,真正的点击菜单然后切换路由页面,这些路由都应该放到main里面去,作为它的children,里面再进行嵌套。所以你看最开始的那个案例,在App.jsx里面使用了<MyNavLink />进行路由跳转,下面就写成了{element}。那么登录成功之后,就进入到main页面中去,里面点击菜单进行路由切换,要么使用hooks,要么定义Link标签,这部分不变,重点是组件的容器变为了<Outlet />。不要以为vue项目中我只是在router/index.js里面注册了路由及组件,整个路由系统就做好了,不是这样的。web的整体布局我要在子组件的地方做
<router-view></router-view>,还要做菜单的路由跳转router.push()这些,只不过是因为PC端都在使用框架,所以这部分都封装好了,我没有做而已,不要想当然了。
作用:根据路由表,动态创建<Routes>和<Route>。
示例代码:
xxxxxxxxxx381//路由表配置:src/routes/index.js2import About from '../pages/About'3import Home from '../pages/Home'4import { Navigate } from 'react-router-dom'56export default [7 {8 path:'/about',9 element:<About/>10 },11 {12 path:'/home',13 element:<Home/>14 },15 {16 path:'/',17 element:<Navigate to="/about"/>18 }19]2021//App.jsx22import React from 'react'23import {NavLink,useRoutes} from 'react-router-dom'24import routes from './routes'2526export default function App() {27 //根据路由表生成对应的路由规则28 const element = useRoutes(routes)29 return (30 <div>31 ......32 {/* 注册路由 */}33 {element}34 ......35 </div>36 )37}38作用:返回一个函数用来实现编程式导航。
示例代码:
xxxxxxxxxx211import React from 'react'2import {useNavigate} from 'react-router-dom'34export default function Demo() {5 const navigate = useNavigate()6 const handle = () => {7 //第一种使用方式:指定具体的路径8 navigate('/login', {9 replace: false,10 state: {a:1, b:2}11 }) 12 //第二种使用方式:传入数值进行前进或后退,类似于5.x中的 history.go()方法13 navigate(-1)14 }15 16 return (17 <div>18 <button onClick={handle}>按钮</button>19 </div>20 )21}3、案例说明:
需求:在Header里面进行路由回退和前进的操作。
分析:Header属于一般组件,是没有办法操作路由的,在router5中,一般路由里面想要操作路由,需要用到withRouter()方法,用到this.props.history上面的go、goForward、goBack等方法。
那么在router6里面该怎么办呢?直接使用useNavigate()这个hook,它提供了方法来操作路由。
实现:
xxxxxxxxxx391// Header/index.jsx23import React from "react";4import { useNavigate } from "react-router-dom";56export default function Header() {7 const navigate = useNavigate();8 function back() {9 navigate(-1);10 }1112 function forward() {13 navigate(1);14 }1516 function go() {17 // 因为Header是一般组件,所以第一个参数要写全。如果是在Message里面使用navigate(),那么只需要写detail。那别的组件呢,与Detail没有嵌套关系的组件?等遇到了再说吧。(2024-08-10 涉及到路由操作,没有别的类型的组件了,要么是路由组件、要么是一般组件。)18 // 如果是params和search传递路由参数,则需要写在path里面。只有state有一个专门的参数来传递。19 navigate("/home/message/detail", {20 // 是否替换路由21 replace: false,22 // 传递state参数23 state: {24 id: 8,25 title: "零零发",26 content: "在天愿作比翼鸟,在地愿为连理枝",27 },28 });29 }3031 return (32 <div className="page-header">33 <h2>React Router Demo</h2>34 <button onClick={back}>←后退</button>35 <button onClick={forward}>前进→</button>36 <button onClick={go}>go</button>37 </div>38 );39}看一下效果:
这个效果看上去有点不对,因为展示了多个detail的链接,应该都保存了才对,怎么点击返回是直接返回news呢?
原因是我使用了state传递路由参数,使用params和search传递参数是正常的。有时间找一下原因吧。
说明:下面的useParams(),useSearchParams(),useLocation(),useMatch()都是与路由传参相关的hook,那么编写路由链接和注册路由有什么变化呢?
在编写路由链接时params和search写法不变,state写法变得更简单了。由于路由表配置的出现,注册路由有了一些变化。如果是直接写<Route />,可以说没有变化,如果你记不清楚了,勤快点,把react-router-dom@5看一下。下面的例子只是Message和Detail的部分,足以说明问题。
xxxxxxxxxx601// /src/routes/index.js 路由表配置23import About from "../pages/About";4import Home from "../pages/Home";5import Message from "../pages/Home/Message";6import News from "../pages/Home/News";7import Detail from '../pages/Home/Message/Detail'8import { Navigate } from "react-router-dom";910const routes = [11 {12 path: "/about",13 element: <About />,14 },15 {16 path: "/home",17 element: <Home />,18 children: [19 {20 path: "news",21 element: <News />,22 },23 {24 path: "message",25 element: <Message />,26 children:[27 // 声明接收params参数。28 // {29 // path:`detail/:id/:title/:content`,30 // element:<Detail />31 // }3233 // search参数无需声明34 // {35 // path:`detail`,36 // element:<Detail />37 // }3839 // state参数无需声明40 {41 path:`detail`,42 element:<Detail />43 }44 ]45 },46 47 // TODO:这里想做一个news的重定向,但是没有效果,查找一下解决办法。48 // {49 // path:"news",50 // element:<Navigate to="news" />51 // }52 ],53 },54 {55 path: "/",56 element: <Navigate to="/about" />,57 },58];5960export default routes;xxxxxxxxxx351// Message/index.js23import React, { useState } from "react";4import { Link, Outlet } from "react-router-dom";56export default function Message() {7 const [messageArr] = useState([8 { id: "1", title: "message001", content: "云想衣裳花想容" },9 { id: "2", title: "message002", content: "春风扶栏露华浓" },10 { id: "3", title: "message003", content: "若非群玉山头见" },11 { id: "4", title: "message004", content: "会向瑶台月下逢" },12 ]);13 return (14 <div>15 <ul>16 {messageArr.map((m) => (17 <li key={m.id}>18 {/* params携带参数 */}19 {/* <Link to={`detail/${m.id}/${m.title}/${m.content}`}>{m.title}</Link> */}2021 {/* search携带参数 */}22 {/* <Link to={`detail?id=${m.id}&title=${m.title}&content=${m.content}`}>{m.title}</Link> */}2324 {/* state携带参数 */}25 <Link to="detail" state={{ id: m.id, title: m.title, content: m.content }}>26 {m.title}27 </Link>28 </li>29 ))}30 </ul>31 <hr />32 <Outlet />33 </div>34 );35}xxxxxxxxxx261// Message/Detail/index.js23import React from "react";4import { useParams,useSearchParams,useLocation } from "react-router-dom"56export default function Detail() {7 // 接受params传递过来的参数8 // const { id, title, content } = useParams();910 // 接收search传递过来的参数11 // const [search] = useSearchParams();12 // const id = search.get("id")13 // const title = search.get("title")14 // const content = search.get("content")1516 // 接收state传递过来的参数,这里是解构赋值的连续写法。17 const {state:{id,title,content}} = useLocation()1819 return (20 <ul>21 <li>ID:{id}</li>22 <li>TITLE:{title}</li>23 <li>CONTENT:{content}</li>24 </ul>25 );26}TODO:里面我搜索到的答案:
参考文档:https://blog.csdn.net/VX_WJ88950106/article/details/126330829
将重定向的path设置为"",就可以了。
xxxxxxxxxx451import About from "../pages/About";2import Home from "../pages/Home";3import Message from "../pages/Home/Message";4import News from "../pages/Home/News";5import Detail from "../pages/Home/Message/Detail";6import { Navigate } from "react-router-dom";78const routes = [9{10path: "/about",11element: <About />,12},13{14path: "/home",15element: <Home />,16children: [17{18path: "news",19element: <News />,20},21{22path: "message",23element: <Message />,24children: [25// state参数无需声明26{27path: `detail`,28element: <Detail />,29},30],31},32// 注意:这里的path是""33{34path: "",35element: <Navigate to="news" />,36},37],38},39{40path: "/",41element: <Navigate to="/about" />,42},43];4445export default routes;但是这个方法有个严重问题:
路由返回时,返回到/home/news就无法返回了,一直卡在这里。
说明解决办法不是这个,还需要另外找办法。
看了一下文档,useRoutes()里面是这么说的:
就是这个属性了:
进行改造:
xxxxxxxxxx41{2index: true,3element: <Navigate to="news" />,4}重定向没有问题,但是还是不能返回/about,等有时间看一下官方examples里面有没有解决办法吧。
@@有一点我没有想到,如果这里设置了
index:true,没有设置path,那<Link>里面的to属性应该怎么写呢???可是react-router5
<Redirect />是可以一直返回的啊。
下面的3、4、5、6中,就说明了各种路由的hook的使用方法。
作用:返回当前匹配路由的params参数,类似于5.x中的match.params。
示例代码:
xxxxxxxxxx161import React from 'react';2import { Routes, Route, useParams } from 'react-router-dom';3import User from './pages/User.jsx'45function ProfilePage() {6 // 获取URL中携带过来的params参数7 let { id } = useParams();8}910function App() {11 return (12 <Routes>13 <Route path="users/:id" element={<User />}/>14 </Routes>15 );16}作用:用于读取和修改当前位置的 URL 中的查询字符串。
返回一个包含两个值的数组,内容分别为:当前的search参数、更新search的函数。
示例代码:
xxxxxxxxxx201import React from 'react'2import {useSearchParams} from 'react-router-dom'34export default function Detail() {5 const [search,setSearch] = useSearchParams()6 const id = search.get('id')7 const title = search.get('title')8 const content = search.get('content')9 return (10 <ul>11 <li>12 <button onClick={()=>setSearch('id=008&title=哈哈&content=嘻嘻')}>点我更新一下收到的search参数</button>13 </li>14 <li>消息编号:{id}</li>15 <li>消息标题:{title}</li>16 <li>消息内容:{content}</li>17 </ul>18 )19}20作用:获取当前 location 信息,对标5.x中的路由组件的location属性。
示例代码:
xxxxxxxxxx241import React from 'react'2import {useLocation} from 'react-router-dom'34export default function Detail() {5 const x = useLocation()6 console.log('@',x)7 // x就是location对象: 8 /*9 {10 hash: "",11 key: "ah9nv6sz",12 pathname: "/login",13 search: "?name=zs&age=18",14 state: {a: 1, b: 2}15 }16 */17 return (18 <ul>19 <li>消息编号:{id}</li>20 <li>消息标题:{title}</li>21 <li>消息内容:{content}</li>22 </ul>23 )24}作用:返回当前匹配信息,对标5.x中的路由组件的match属性。这个虽然可以使用,但是相对于useParams() 来说复杂一些,所以多使用useParams()。
示例代码:
xxxxxxxxxx251<Route path="/login/:page/:pageSize" element={<Login />}/>2<NavLink to="/login/1/10">登录</NavLink>34export default function Login() {5 const match = useMatch('/login/:x/:y')6 console.log(match) //输出match对象7 //match对象内容如下:8 /*9 {10 params: {x: '1', y: '10'}11 pathname: "/LoGin/1/10" 12 pathnameBase: "/LoGin/1/10"13 pattern: {14 path: '/login/:x/:y', 15 caseSensitive: false, 16 end: false17 }18 }19 */20 return (21 <div>22 <h1>Login</h1>23 </div>24 )25}下面的4个hook,了解一下即可,用的不多。
作用:如果组件在 <Router> 的上下文中呈现,则 useInRouterContext 钩子返回 true,否则返回 false。
怎么理解?一般项目中如果用到router,那么都会用<BrowserRouter>或<HashRouter>将<App />给包裹住,里面的组件不分路由组件、一般组件,使用useInRouterContext()返回的都是true。
使用场景:比如说你写了一个第三方库,你要知道使用者是不是在路由里面使用的,就可以做相应的处理。
POP、PUSH、REPLACE。POP是指在浏览器中直接打开了这个路由组件(刷新页面)。作用:用来呈现当前组件中渲染的嵌套路由。
示例代码:
xxxxxxxxxx41const result = useOutlet()2console.log(result)3// 如果嵌套路由没有挂载,则result为null4// 如果嵌套路由已经挂载,则展示嵌套的路由对象react-router里面并不是只有这么多写法,我看到的是别人使用createBrowserRouter来配置路由表,写项目时用一下。