React 路由管理方案 :react-router-dom
理解react-router-dom
react是做两端的,一个是web端「html5页面」,另外一个是Native App「原生App端」
以recat为核心,react-dom就是处理web端的,react-native 就是处理Native App端的,对应的路由也分为react-router-dom「主要针对web端的路由管理机制」和react-router-native。
安装 npm i react-router-dom --save
为什么会有react-router-dom
@1 当前前端开发,大部分以SPA单页面应用开发为主「如管理系统,移动端的web app或者其他app:SPA单页面应用」,SPA单页面应用要依托于react-router-dom前端路由机制,帮助我们实现 通过一些操作如 点击操作,改变路由地址的哈希值;通过地址的变化,让当前页面呈现不同的组件。保证在整个页面不跳转的情况下,呈现不同的效果。
SPA 「Single Page Application」单页面应用
对应的是MPA 「Multi Page Application」多页面应用
单页面应用
- 组成结构:一个主页面+多个组件「只是在页面中的某个区域渲染不同的组件,我们只是渲染或者销毁组件」
- 地址模式:www.xxx.com/#/login www.xxx.com/#/home
- 跳转方式: 主页面不刷新,只是组件之间的切换「一个组件渲染/显示,一个组件销毁/隐藏」
- 内容更新:只是相关组件的切换,即局部更新
- 公共资源: 公共资源只需要加载一次
- 数据体验: 组件切换流畅,操作体验好,容易实现过渡动画
- 数据传递: 方式有很多「本地存储、全局变量、组件通信等」
- 开发成本: 需要基于特定的框架开发,维护起来非常方便
- 搜索引擎: 不利于SEO「搜索引擎」优化(有否利于搜索引擎的优化,主要在于数据是由客户端渲染还是服务器渲染,SPA单页面应用大部分是由客户端渲染的,通过JS动态绑定的,在页面源代码中没有这些信息,没有这些信息就不能被搜索引擎收录,也就无法进行SEO优化)「可基于SSR服务器渲染解决」
- 使用场景:目前主流模式,对体验度和流畅度要求较高的应用,例如:移动端开发、PC端管理系统等
多页面应用
- 组成结构:许多完整的页面
- 地址模式:www.xxx.com/login.html www.xxx.com/index.html
- 跳转方式: 从一个页面跳转到另外一个页面
- 内容更新:整个HTML页面的切换,即全局刷新
- 公共资源: 公共资源需要重新加载
- 数据体验: 操作体验差、页面切换不流畅、实现不了过渡动画
- 数据传递: 方法有限,可基于本地存储、URL问号传参等方案
- 开发成本: 直接开发即可,但后期维护很麻烦
- 搜索引擎: 可以直接做SEO优化「源代码中有,就会被搜索引擎收录,就可以做SEO优化」
- 使用场景:非主流模式,适用于对SEO要求较高的应用,例如:传统的PC官网等
路由设计模式 「原生JS实现路由管理机制」
在不使用react-router-dom前端路由管理机制,想要自己实现单页面中不同组件的切换的 路由机制
方案1. 哈希路由(hash)
原理:每一次路由跳转,都是改变页面的hash值,并且监听hashchange事件,渲染不同的内容!!只是改变页面的hash值,页面是不刷新的。
路由机制1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>前端路由机制实现</title>
</head>
<body>
<nav class="nav-box">
<a href="#/">首页</a>
<a href="#/product">产品中心</a>
<a href="#/personal">个人中心</a>
</nav>
<div class="view-box"></div>
<!-- IMPORT JS -->
<script>
/* HASH 路由
+ 改变页面的哈希值(#/xxx)「路由切换/跳转」,页面是不会刷新的
+ 根据不同的哈希值,让容器中渲染不同的内容「组件」
*/
//获取渲染内容的容器
const viewBox = document.querySelector('.view-box')
//构建一个路由匹配表:每当我们重新加载页面,或者路由切换(切换hash值)的时候,都先到路由匹配表中进行匹配,根据当前页面的hash值匹配出要渲染的内容「组件」!
//路由匹配表一个数组,里面每一个匹配规则都是一个对象,path指的是路由值,component指要渲染的内容「通常指要渲染的组件」
const routes = [
{
path: '/',
component: '首页的内容'
}, {
path: '/product',
component: '产品中心的内容'
}, {
path: '/personal',
component: '个人中心的内容'
}
]
//路由匹配的办法
const routerMatch = function routerMatch() {
//通过location.hash.substring(1)方法获取当前页面的哈希值「#后面的内容」
let hash = location.hash.substring(1),
text = "";
//循环遍历路由匹配表,将每一项的path值于获取到的页面hash值进行比较,相同时获取对应项的component值,渲染到内容区域
routes.forEach(item => {
if (item.path === hash) {
text = item.component
}
})
viewBox.innerHTML = text
}
//一进来要展示的是首页的信息,所以默认改变一下hash值
location.hash = '/'
//location.hash拿到的就是hash值 “#/”
//刚开始就执行路由匹配的方法,让首页内容出现在内容区域
routerMatch()
//监测hash值的变化,重新进行路由匹配
//根据window.onhashchange事件监听hash变化,事件函数就是路由匹配的方法
window.onhashchange = routerMatch
</script>
</body>
</html>
方案2. History 路由 「浏览器路由」
在H5中,JS中新增了一个History对象
History {length: 1, scrollRestoration: ‘auto’, state: null}
沿着原型链查找到History,在history对象中,提供了页面地址切换的方法
里面有back方法「后退」、forward方法「前进」、go方法、pushState方法「实现页面地址的跳转」、replaceState方法
- history.pushState({},“”,“/personal”) (data,title,url地址) 地址跳转,向历史记录池中新增历史记录
- history.back() = history.go(-1) 后退 返回上一级
- history.forward()=history.go(1) 前进
- history.go(n) n为数字,1/-1
- history.replaceState({},“”,“/cart”) 也实现地址跳转,但是并不是新增历史记录,而是替换当前现有的记录
通过location.pathname获取当前的页面的最新地址
react-router-dom
V5版本和V6版本区别较大
V5版本
安装时注意版本号
基础运用
- <
Link
>等组件,需要放在 Router(BrowserRouter/HashRouter)的内部 - 每当页面加载或者路由切换的时候,都会去和每一个<
Route
>进行匹配
- 和其中一个匹配成功后,还会继续向下匹配,所以需要基于<
Switch
>处理 - 默认是“非精准”匹配的,所以我们需要基于exact 处理
项目解析
项目文件中src下的index.js是程序的入口
一般新建一个文件,作为视图入口,里面存放渲染的组件「可以视为是单页面的那个唯一的页面」
然后在入口中导入该文件再进行渲染。
新建一个文件夹,用于创建所需的组件
多级路由的概念
多级路由分散在各个组件中
多级路由统一管理
我们期望,在React项目中,可以对多级路由进行统一管理
Vue中的路由就是统一管理的:
- 路由表
- 导航守卫
React中的路由,默认是分散到各个组件中配置的,想要统一管理,需要手动操作。
在src文件夹下新建文件夹router,在里面对路由进行管理
文件夹中新建index.js「统一管理路由」
routes.js 「配置路由表」
react中的路由懒加载方案
在真实项目中,如果我们事先把所有组件全部导入进来,再基于Route做路由配置,这样:最后项目打包的时候,所以组件全部打包到一个js中!!
这样JS会非常大:
- 第一次加载页面的时候,从服务器获取这个JS文件就会用很久的时间,导致此阶段,页面一直处于白屏的状态,这样很不好。
虽然说优化方案中,有建议合并为一个JS文件,这样减少HTTP网络请求的次数!!但是这个文件不易过大!
最好的处理方案是这样的:
- 我们只把最开始要展示的内容/组件打包到“主JS”中「bundle.js」,其余的组件,打包成独立的JS「或者几个组件合并在一起打包」
- 当页面加载的时候,首先只把主JS”「bundle.js」请求回来渲染,其余的JS先不加载
- 因为bundle.js中只有最开始要渲染组件的代码,所以体积小,获取和渲染速度快,可以减少白屏等待的时间!!
- 其余的JS此时并没有加载,也不影响页面第一次渲染
- 当进行路由切换的时候,和哪个规则匹配,想要渲染哪个组件,再把这个组件所在的JS文件,动态导入进来进行渲染即可!!
=========
分割打包JS、按需异步加载JS==> 路由懒加载
通过react中的lazy的方法,实现懒加载
组件的值是lazy方法(里面是一个函数,函数执行返回的结果是导出「import」对应的要渲染的组件)执行后的结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cr5g2VVT-1692965798121)(./路由懒加载.png)]
真实项目中,我们一定要做路由懒加载!!但是很少,每个组件就是一个JS,我们会几个组件打包在一起!!
打包规则一般按模块。
在react-route-dom v5中,基于Route路由匹配渲染的组件,路由会默认给每个组件传递三个属性
<Route path="/a" component={A}>
给A组件传递三个属性
- history
- location
- match
后期我们基于props「函数组件」/this.props「类组件」获取传递的属性值!
如果是通过render渲染的组件,需要手动传递给组件
<Route path="/a" render={(props)=>{
//在render中可以获取传递的属性
//但是在组件中没有这些属性,我们需要自己传递给组件
return <A {...props}/>
}}>
总结:基于<Route
>匹配渲染的组件,我们想获取这三个属性对象
@1 基于props属性获取,适用于函数组件和类组件
@2 基于Hook函数获取,只适用于函数组件
在组件中获取路由对象信息
总结:所以组件最好都包裹在<HashRouter
>/<BrowserRouter
>中,只有这样的组件,我们才能在每一个组件中,获取history/loaction/match等对象信息!!
- 如果是函数组件,并且是基于<
Route
>匹配渲染的
- 基于props属性获取「render渲染的,需要自己处理一下」
- 基于useHistory/useLocation/useRouteMatch这些Hook函数获取~
- 函数组件,但并不是基于<
Route
>匹配渲染的
- 基于Hook函数获取
- 基于withRouter 代理一下这个组件,就可以基于props获取了
- 类组件,Hook函数方案被pass,只能基于props获取,但是如果其没有被<
Route
>匹配渲染,则需要基于withRouter 代理一下这个组件!!
路由跳转及传参方案
路由跳转
方案1 Link跳转
方案2 编程式导航 「history」
传参方案
方案1 问号传参
方案2 路径参数
NavLink
NavLink VS Link
都是实现路由跳转的,语法上几乎一样,区别就是:
每一次页面加载或者理由切换完毕,都会拿着最新的路由地址,和NavLink中的to指定的地址「或者pathname地址」进行匹配
- 匹配上的这一项,会默认设置active选中样式类「我们可以基于activeClassName重新设置选中的样式类名」
- 我们也可以设置exact精准匹配
基于这样的机制,我们就可以给选中的导航设置相关的选中样式
Router V6版本 特点及案例
思想同5版本,语法大不相同
在react-router-dom V6中常用的路由Hook:
- useNavigate =>代替5中的 useHistory :实现编程式导航
- useLocation 「5中也有」:获取location 对象信息 pathname/search/state…
- useSearchParams 「新增的」 获取问号传参的信息,取得的结果是一个URLSearchParams对象,里面有get等方法
- useParams 「5中也有」:获取路径参数匹配的信息
- useMatch(pathname) => 代替5中的useRouteMatch 「5中的这个Hook有用,可以基于params获取路径参数匹配的信息,但是在6中,这个Hook需要我们自己传递地址,而且params也没有获取路径参数匹配的信息,用的比较少!!」
在react-router-dom V6版本中,移除了:
- Switch
- Redirect ->代替方案:Navigate
- withRouter ->代替方案:自己写一个withRouter
路由匹配
所有的路由匹配规则,放在<Routes
>中,每一条规则的匹配还是基于<Route
>
+ 路由匹配成功,不再基于component/render控制,而是基于element,语法格式是
+ 不再需要Switch,默认就是一个匹配成功,就不再匹配下面的了
+ 不再需要exact,默认每一项匹配都是精准匹配
原有的操作,被 代替!!
+ 遇到组件,路由就会跳转,跳转到to指定的路由地址
+ 可以给设置replace 属性,贼不会新增历史记录,而是替换现有记录
+ 中的to属性还可以写成对象的形式:pathname: 需要跳转的信息 search 问号传参信息
多级路由
v6版本中,要求所有的路由(二级或者多级路由),不再分散到各个组件中编写,而是统一在一起进行处理!!
v6reactroutedon/src/views/A.js
import React from "react"
import {styled} from "styled-components"
import { NavLink,Outlet } from "react-router-dom"
const DemoBox=styled.div`
display: flex;
font-size: 12px;
.menu{
a{
font-size: 12px;
color: #000;
display: block;
&.active{
color: green;
}
}
}
`
const A=function A(){
return <DemoBox className="box">
<div className="menu">
<NavLink to="/a/a1">A1</NavLink>
<NavLink to="/a/a2">A2</NavLink>
<NavLink to="/a/a3">A3</NavLink>
</div>
<div className="view">
{/* Outlet:路由容器,用来渲染二级「或者多级」路由匹配的内容 */}
<Outlet/>
</div>
</DemoBox>
}
export default A
获取路由信息
在react-route-dom V6中,即便当前组件是基于<Route
>匹配渲染的,也不会基于属性,把history/location/match 传递给组件!! 想获取相关信息,我们只能基于Hook函数处理!!
- 首先要确保,需要使用“路由Hook”的组件,是在Router「HashRouter/BrowserRouter」内部包着的组件,否者使用这些Hook会报错!!
- 只要是在Rouer内部包裹的的组件,不论是否是基于<
Route
>匹配渲染的组件- 默认都不可能再基于props获取相关的对象信息
- 只能基于 “路由hook” 去获取
为了方便在类组件中也可以获取路由的相关信息:
- 之后我们构建路由表的时候,会想办法:继续让基于<
Router
>匹配渲染的组件可以基于属性获取需要的信息,
2.不是基于<Route
>匹配渲染的组件,我们需要自己重写withRouter「v6中干掉了这个API」,让其和基于<Route
>匹配渲染的组件具备相同的属性!!
路由跳转的方式
在react-route-dom V6中,,实现路由跳转的方式:
- <Link/NavLink to=“/a”> 点击跳转路由
- 遇到这个组件就会跳转
- 编程式导航
v6版本中取消了useHistory Hook函数,也没有history对象了,基于navigate函数实现路由跳转
import {useNavigate } from “react-router-dom”
const navigate=useNavigate()
// console.log(navigate-->
, navigate);//是一个函数,第一个参数是to,要跳转的地址「可以是个地址,也可以是个对象」,第二个参数是options 配置项「replace」
navigate(“/c”);
navigate(“/c”,{replace:true})
navigate({
pathname:‘/c’
})
navigate({
pathname:‘/c’,
search:“?id=100&name=zhufeng”
})
…
路由跳转过程中传参方案
- 问号传参
- 路径参数
- 隐式传参
问号传参
//问号传参
navigate({
pathname:'/c',
search:qs.stringify({id:100,
name:'zhufeng'})
})
对应的获取传递的信息的方法
方案一 useLocation + URLSearchParams /+ qs.parse
//接收问号传参信息
let location=useLocation()
//console.log(`location-->`, location); //对象
//location.search 问号传参的信息
const usp=new URLSearchParams(location.search)
console.log(usp.get("id"),usp.get("name"));
//拿到location.search之后用qs.parse 处理也可以
方案二 useSearchParams 「更简单」
let [usp]=useSearchParams()
//console.log(useSearchParams());//返回的结果是一个数组 第一项是对应的实例对象 ,里面有get等方法
console.log(usp.get("id"),usp.get("name"))
路径参数
//路径参数 {把信息作为地址的一部分}在对应的Route中 path属性值进行添加路径参数 /:id?/:name? 通过navigate 把100传递给id...
navigate(`/c/100/zhufeng`)
方案1 useLocation + useMatch「不能获取」
const location=useLocation()
const match=useMatch(location.pathname)//useMatch中必须要传递一个地址进来,它是对地址进行解析
console.log(match,location.pathname);//match是个对象,无法获取路径参数信息,即便在match.params也没有这个信息,location.pathname是当前的地址 "/c/100/zhufeng"
方案2 useParams
const params=useParams()
console.log(`params-->`, params); //=> {id: '100', name: 'zhufeng'}
隐式传参
//隐式传参 不同于V5的history.push 而是在navigate的配置项中设置
//基于这种方式,在Router5中,目标组件只要刷新,传递的信息就消失了,但在Router6中,这个隐式传递的信息,却被保留下来了
navigate('/c',{
replace:true,//历史记录池中替换现有地址
//隐式传参信息
state:{
id:100,
name:'zhufeng'
}
})
对应的获取传递信息的方法
const location=useLocation()
console.log(location.state);//{id: 100, name: 'zhufeng'}
v6reactroutedon/src/views/B.js
import React from "react"
import {useNavigate } from "react-router-dom"
import qs from "qs"
const B=function B(props){
//console.log(props); //不可以
//console.log(useLocation()) 可以获取路由对象信息
//经过构建路由表,此时可以通过props获得路由对象信息 ,此时可以不经过hook函数获取navigate,直接通过props解构 除navigate,然后在点击事件中直接使用进行跳转并传参
/*
<button onClick={()=>{
navigate(`/c/100/zhufeng`)
}}>按钮</button>
*/
console.log(props);//{location: {…}, params: {…}, usp: URLSearchParams, navigate: ƒ}
//const navigate =useNavigate()
//const handle=()=>{
/* //问号传参
navigate({
pathname:'/c',
search:qs.stringify({id:100,
name:'zhufeng'})
}) */
/* //路径参数 {把信息作为地址的一部分}在对应的Route中 path属性值进行添加路径参数 /:id?/:name? 通过navigate 把100传递给id...
navigate(`/c/100/zhufeng`) */
//隐式传参 不同于V5的history.push 而是在navigate的配置项中设置
//基于这种方式,在Router5中,目标组件只要刷新,传递的信息就消失了,但在Router6中,这个隐式传递的信息,却被保留下来了
/* navigate('/c',{
replace:true,//历史记录池中替换现有地址
//隐式传参信息
state:{
id:100,
name:'zhufeng'
}
}) */
const {navigate}=props
return <div className="box">
B组件信息
{/* <button onClick={handle}>按钮</button> */}
<button onClick={()=>{
navigate(`/c/100/zhufeng`)
}}>按钮</button>
</div>
}
export default B
/* 在react-route-dom V6中,,实现路由跳转的方式:
+ <Link/NavLink to="/a"> 点击跳转路由
+ <Navigate to="/a" /> 遇到这个组件就会跳转
+ 编程式导航 v6版本中取消了useHistory Hook函数,也没有history对象了,基于navigate函数实现路由跳转
import {useNavigate } from "react-router-dom"
const navigate=useNavigate()
// console.log(`navigate-->`, navigate);//是一个函数,第一个参数是to,要跳转的地址「可以是个地址,也可以是个对象」,第二个参数是options 配置项「replace」
navigate("/c");
navigate("/c",{replace:true})
navigate({
pathname:'/c'
})
navigate({
pathname:'/c',
search:"?id=100&name=zhufeng"
})
.....
*/
/*
在react-route-dom V6中,即便当前组件是基于<Route>匹配渲染的,也不会基于属性,把history/location/match 传递给组件!! 想获取相关信息,我们只能基于Hook函数处理!!
+ 首先要确保,需要使用“路由Hook”的组件,是在Router「HashRouter/BrowserRouter」内部包着的组件,否者使用这些Hook会报错!!
+ 只要是在Rouer内部包裹的的组件,不论是否是基于<Route>匹配渲染的组件
+ 默认都不可能再基于props获取相关的对象信息
+ 只能基于 “路由hook” 去获取
为了方便在类组件中也可以获取路由的相关信息:
1. 之后我们构建路由表的时候,会想办法:继续让基于<Router>匹配渲染的组件可以基于属性获取需要的信息,
2.不是基于<Route>匹配渲染的组件,我们需要自己重写withRouter「v6中干掉了这个API」,让其和基于<Route>匹配渲染的组件具备相同的属性!!
*/
v6reactroutedon/src/views/C.js
import { Divider } from "antd"
import {useLocation,useSearchParams,useMatch,useParams}from "react-router-dom"
const C=function C(props){
const {params}=props;
console.log(props);
console.log(`params-->`, params);
/* //接收问号传参信息
let location=useLocation()
//console.log(`location-->`, location); //对象
//location.search 问号传参的信息
const usp=new URLSearchParams(location.search)
console.log(usp.get("id"),usp.get("name"));
//拿到location.search之后用qs.parse 处理也可以*/
/* let [usp]=useSearchParams()
//console.log(useSearchParams());//返回的结果是一个数组 第一项是对应的实例对象 ,里面有get等方法
console.log(usp.get("id"),usp.get("name")) */
/* //useLocation +useMatch {不能获取}
const location=useLocation()
const match=useMatch(location.pathname)//useMatch中必须要传递一个地址进来,它是对地址进行解析
console.log(match,location.pathname);//match是个对象,无法获取路径参数信息,即便在match.params也没有这个信息,location.pathname是当前的地址 "/c/100/zhufeng" */
/* const params=useParams()
console.log(`params-->`, params); //=> {id: '100', name: 'zhufeng'} */
/* const location=useLocation()
console.log(location.state);//{id: 100, name: 'zhufeng'} */
return <div className="box">
C组件内容</div>
}
export default C
知识点 qs
qs是一个url参数转化(parse和stringify)的js库
- qs.parse
qs.parse 方法可以把一段格式化的字符串转换为对象格式,比如
let url = 'http://item.taobao.com/item.htm?a=1&b=2&c=&d=xxx&e';
let data = qs.parse(url.split('?')[1]);
// data的结果是
{
a: 1,
b: 2,
c: '',
d: xxx,
e: ''
}
- qs.stringify
qs.stringify 则和 qs.@1 parse 相反,是把一个参数对象格式化为一个字符串。
let params = { c: 'b', a: 'd' };
qs.stringify(params)
// 结果是
'c=b&a=d'
@2 排序
甚至可以对格式化后的参数进行排序:
qs.stringify(params, (a,b) => a.localeCompare(b))
// 结果是
'a=b&c=d'
等等
知识点 URLSearchParams
URLSearchParams 是一个 JavaScript 内置的对象,用于处理 URL 查询参数。它提供了一组方法来解析、处理和操作 URL 的查询部分。
使用方法如下:
- 创建一个 URLSearchParams 对象:
const params = new URLSearchParams();
- 添加查询参数:
params.append('key1', 'value1');
params.append('key2', 'value2');
- 获取查询参数:
params.get('key1'); // 返回 'value1'
params.getAll('key2'); // 返回 ['value2']
- 删除指定查询参数:
params.delete('key2');
- 检查是否存在指定查询参数:
params.has('key1'); // 返回 true
params.has('key2'); // 返回 false
- 迭代所有查询参数:
for (const [key, value] of params) {
console.log(`${key}: ${value}`);
}
- 将 URLSearchParams 对象作为 URL 的查询参数部分:
const url = new URL('http://example.com');
url.search = params.toString();
console.log(url.href); // 输出 "http://example.com/?key1=value1"
路由表及统一管理
路由表「动态路由、路由懒加载」
新建v6reactroutedon/src/router
里面新建v6reactroutedon/src/router/routes.js文件 搭建路由表
//搭建路由表
import {Navigate}from "react-router-dom"
import {lazy} from 'react'
import A from "../views/A";
//A 版块的二级路由
const aRoutes=[{
path:"/a",
component:()=><Navigate to="/a/a1" />
},{
path:"/a/a1",
name:'a-a1',
component:lazy(()=>import(/* webpackChunkName:"AChild" */"../views/a/A1")),
meta:{}
},{
path:"/a/a2",
name:'a-a2',
component:lazy(()=>import(/* webpackChunkName:"AChild" */"../views/a/A2")),///* webpackChunkName:"AChild" */3个组件打包在一起
meta:{}
},{
path:"/a/a3",
name:'a-a3',
component:lazy(()=>import(/* webpackChunkName:"AChild" */"../views/a/A3")),
meta:{}
}]
//A 版块的一级路由
const routes=[{
path:'/',
component:()=>{<Navigate to="/a" />}//由于遇到Navigate直接跳转,所以要用函数包起来,等匹配的时候执行
},{
path:'/a',
name:'a',
component:A,//这样只是导入A组件,并非执行 <A/> 这样才算调用执行
meta:{},
children:aRoutes
},{
path:'/b',
name:'b',
component:lazy(()=>import('../views/B')),// 单独打包一个JS 按需导入 懒加载
meta:{},
},{
path:'/c/:id?/:name?',
name:'c',
component:lazy(()=>import('../views/C')),// 单独打包一个JS 按需导入 懒加载
meta:{},
},{
path:'*',
component:()=>{
return <Navigate to={{
pathname:"/a",
search:'?lx=404'
}} />
}
}]
export default routes;
v6reactroutedon/src/router/index.js 递归创建Route
import routes from "./routes";
import {Routes,Route,useNavigate,useLocation,useParams,useSearchParams} from "react-router-dom"
//路由懒加载 需要Suspense组件配合才能实现
import { Suspense } from "react";
/* 统一渲染的组件:在这里可以做一些事情,「例如权限/登录态校验,传递路由信息的属性...」 */
const Element=function Element(props){
let {component:Component}=props;
//特殊事情: 把路由信息先获取到,最后基于属性传递给组件「这样只要是基于<Route>匹配渲染的组件「不管是类组件还是函数组件」都可以基于属性获取到路由信息」
const navigate=useNavigate(),
location=useLocation(),
params=useParams(),
[usp]=useSearchParams()
//最后要把Component进行渲染
return <Component navigate={navigate} location={location} params={params} usp={usp}/>
}
/* 递归创建Route */
const createRoute=function createRoute(routes){
//每一次路由匹配成功,不直接渲染我们设定的组件,而是渲染Element;在Element做一些特殊处理后,再去渲染我们真实要渲染的组件!!
return<>
{routes.map((item,index)=>{
let {path,children}=item
return <Route key={index} path={path} element={<Element {...item} />}>
{/* 基于递归的方式,绑定子集路由 */}
{Array.isArray(children)?createRoute(children):null}
</Route>
})}
</>
}
/* 创建路由容器 */
export default function RouterView(){
return<Suspense fallback={<>正在加载中...</>}>
<Routes>
{createRoute(routes)}
</Routes>
</Suspense>
}
/* 创建withRouter */
export const withRouter=function withRouter(Component){
//Component:真实要渲染的组件
return function HOC(props){
const navigate=useNavigate(),
location=useLocation(),
params=useParams(),
[usp]=useSearchParams()
return <Component navigate={navigate} location={location} params={params} usp={usp} />
}
}
/* 路由表配置完成后
函数组件 & 基于Route匹配渲染的,可以基于props获取路由信息,也可以自己使用Hook函数获取
类组件 & 基于Route匹配渲染的,只能基于属性获取,或者使用withRouter「自己写的」
函数组件 & 不是Route匹配的:可以基于Hook自己处理,也可以使用withRoute
类组件 & 不是Route匹配的:只能使用withRouter
------
都要放在<HashRouter>内部
*/
配置路由表前的v6reactroutedon/src/App.js
import React from "react"
import { HashRouter, Routes, Route, Navigate } from "react-router-dom"
import HomeHead from "./components/HomeHead";
import A from "./views/A"
import B from "./views/B"
import C from "./views/C";
import A1 from "./views/a/A1";
import A2 from "./views/a/A2";
import A3 from "./views/a/A3";
/* 导入需要的组件 */
function App() {
return (
<HashRouter>
<HomeHead />
<div className="content">
{/*
所有的路由匹配规则,放在<Routes>中,每一条规则的匹配还是基于<Route>
+ 路由匹配成功,不再基于component/render控制,而是基于element,语法格式是<Component/>
+ 不再需要Switch,默认就是一个匹配成功,就不再匹配下面的了
+ 不再需要exact,默认每一项匹配都是精准匹配
原有的<Redirect>操作,被<Navigate to="/a" /> 代替!!
+ 遇到<Navigate />组件,路由就会跳转,跳转到to指定的路由地址
+ 可以给<Navigate />设置replace 属性,贼不会新增历史记录,而是替换现有记录
+ <Navigate to={{}}/>中的to属性还可以写成对象的形式:pathname: 需要跳转的信息 search 问号传参信息
*/}
<Routes>
<Route path="/" element={<Navigate to="/a" />} />
<Route path="/a" element={<A />}>
{/* v6版本中,要求所有的路由(二级或者多级路由),不再分散到各个组件中编写,而是统一在一起进行处理!! */}
<Route path="/a" element={<Navigate to="/a/a1" />} />
<Route path="/a/a1" element={<A1 />}/>
<Route path="/a/a2" element={<A2 />}/>
<Route path="/a/a3" element={<A3 />}/>
</Route>
<Route path="/b" element={<B />}/>
<Route path="/c/:id?/:name?" element={<C />}/>
{/* 如果以上都不匹配 ,我们可以渲染404组件,或者重定向到A组件「传递不同的问号信息」*/}
<Route path="*" element={<Navigate to={{
pathname:"/a",
search:'?lx=404'
}} />}/>
</Routes>
</div>
</HashRouter>
);
}
export default App;
配置路由表 后 的v6reactroutedon/src/App.js
import React from "react"
import { HashRouter} from "react-router-dom"
import HomeHead from "./components/HomeHead";
import RouterView from "./router";
function App() {
return (
<HashRouter>
<HomeHead />
<div className="content">
<RouterView/>
</div>
</HashRouter>
);
}
/* //或者直接使用useRoutes,直接写路由表,帮我们节省了循环动态创建Route的操作
import {useRoutes,Navigate} from "react-router-dom"
import A from "./views/A";
import A1 from "./views/a/A1";
const App=function App(){
const element=useRoutes([{
path:'/',
element:<Navigate to="/a"
/> },{
path:'/a',
element:<A />,
children:[{
path:'/a',
element:<Navigate to="/a/a1" />
},{
path:'/a/a1',
element:<A1 />
}]
}])
return <>
<HomeHead/>
{element}
</>
} */
export default App;
//修改完毕后,在各组件中都可以通过props拿到路由信息
配置路由表 后 的v6reactroutedon/src/components/HomeHead.js
import React from "react";
import { NavLink } from "react-router-dom";
import styled from "styled-components";
import { withRouter } from "../router";
const NavBox = styled.nav`
a{
margin-right: 10px;
color: #000;
&.active{
color: red;
}
}
`;
const HomeHead = function HomeHead(props){
console.log(props);
return<NavBox>
<NavLink to="/a">A</NavLink>
<NavLink to="/b">B</NavLink>
<NavLink to="/c">C</NavLink>
</NavBox>
}
export default withRouter(HomeHead)
/*HomeHead 函数组件,在HashRouter中,未经过Route匹配渲染
可以基于Hook函数获取
或者withRouter在导出的时候将本组件包起来
组件内部就可以通过属性拿到路由信息了
*/
useReducer Hook函数
是对useState 的升级处理
useReducer和redux基础操作,几乎一模一样!!
- 普通需求处理的时候,基本都是useState直接处理,不会使用useReducer
- 但是如果一个组件的逻辑很复杂,需要大量的状态/修改状态的逻辑,此时使用useReducer管理这些状态会更好一些
@1 不需要再基于useState 一个个的去创建状态了
@2 所有状态修改的逻辑,全部统一话处理了!!
v6reactroutedon/src/views/a/A1.js
import React,{useState,useReducer} from "react"
const initialState={
num:0
}
const reducer=function reducer(state,action){
state={...state}
switch(action.type){
case "plus":
state.num++
break;
case "minus":
state.num--
break
default:
}
return state
}
const A1=function A1(){
/* let [num,setNum]=useState(0)
return <div className="box">
A1组件的详细信息
<span>{num}</span>
<br/>
<button onClick={()=>{
setNum(num+1)
}}>增加</button>
<button onClick={()=>{
setNum(num-1)
}}>减少</button>
</div> */
let [state,dispatch]=useReducer(reducer,initialState)
return <div className="box">
A1组件的详细信息
<span>{state.num}</span>
<br/>
<button onClick={()=>{
dispatch({type:'plus'})
}}>增加</button>
<button onClick={()=>{
dispatch({type:'minus'})
}}>减少</button>
</div>
}
export default A1
/*
useReducer 是对useState 的升级处理
useReducer和redux基础操作,几乎一模一样!!
+ 普通需求处理的时候,基本都是useState直接处理,不会使用useReducer
+ 但是如果一个组件的逻辑很负责,需要大量的状态/修改状态的逻辑,此时使用useReducer管理这些状态会更好一些
@1 不需要再基于useState 一个个的去创建状态了
@2 所有状态修改的逻辑,全部统一话处理了!!
*/