React 路由管理方案 React-Router-Dom

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版本

安装时注意版本号
基础运用

  1. <Link>等组件,需要放在 Router(BrowserRouter/HashRouter)的内部
  2. 每当页面加载或者路由切换的时候,都会去和每一个<Route>进行匹配
  • 和其中一个匹配成功后,还会继续向下匹配,所以需要基于<Switch>处理
  • 默认是“非精准”匹配的,所以我们需要基于exact 处理

项目解析
项目文件中src下的index.js是程序的入口
一般新建一个文件,作为视图入口,里面存放渲染的组件「可以视为是单页面的那个唯一的页面」
然后在入口中导入该文件再进行渲染。
新建一个文件夹,用于创建所需的组件

多级路由的概念

多级路由分散在各个组件中

多级路由统一管理

我们期望,在React项目中,可以对多级路由进行统一管理
Vue中的路由就是统一管理的:

  • 路由表
  • 导航守卫
    React中的路由,默认是分散到各个组件中配置的,想要统一管理,需要手动操作。

在src文件夹下新建文件夹router,在里面对路由进行管理
文件夹中新建index.js「统一管理路由」
routes.js 「配置路由表」

react中的路由懒加载方案

在真实项目中,如果我们事先把所有组件全部导入进来,再基于Route做路由配置,这样:最后项目打包的时候,所以组件全部打包到一个js中!!
这样JS会非常大:

  • 第一次加载页面的时候,从服务器获取这个JS文件就会用很久的时间,导致此阶段,页面一直处于白屏的状态,这样很不好。
    虽然说优化方案中,有建议合并为一个JS文件,这样减少HTTP网络请求的次数!!但是这个文件不易过大!
    最好的处理方案是这样的:
  1. 我们只把最开始要展示的内容/组件打包到“主JS”中「bundle.js」,其余的组件,打包成独立的JS「或者几个组件合并在一起打包」
  2. 当页面加载的时候,首先只把主JS”「bundle.js」请求回来渲染,其余的JS先不加载
  • 因为bundle.js中只有最开始要渲染组件的代码,所以体积小,获取和渲染速度快,可以减少白屏等待的时间!!
  • 其余的JS此时并没有加载,也不影响页面第一次渲染
  1. 当进行路由切换的时候,和哪个规则匹配,想要渲染哪个组件,再把这个组件所在的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等对象信息!!

  1. 如果是函数组件,并且是基于<Route>匹配渲染的
  • 基于props属性获取「render渲染的,需要自己处理一下」
  • 基于useHistory/useLocation/useRouteMatch这些Hook函数获取~
  1. 函数组件,但并不是基于<Route>匹配渲染的
  • 基于Hook函数获取
  • 基于withRouter 代理一下这个组件,就可以基于props获取了
  1. 类组件,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” 去获取

为了方便在类组件中也可以获取路由的相关信息:

  1. 之后我们构建路由表的时候,会想办法:继续让基于<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”
    })
路由跳转过程中传参方案
  1. 问号传参
  2. 路径参数
  3. 隐式传参
问号传参
//问号传参
  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库

  1. 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: ''
}

  1. 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 的查询部分

使用方法如下:

  1. 创建一个 URLSearchParams 对象:
const params = new URLSearchParams();
  1. 添加查询参数:
params.append('key1', 'value1');
params.append('key2', 'value2');
  1. 获取查询参数:
params.get('key1'); // 返回 'value1'
params.getAll('key2'); // 返回 ['value2']
  1. 删除指定查询参数:
params.delete('key2');
  1. 检查是否存在指定查询参数:
params.has('key1'); // 返回 true
params.has('key2'); // 返回 false

  1. 迭代所有查询参数:
for (const [key, value] of params) {
  console.log(`${key}: ${value}`);
}
  1. 将 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 所有状态修改的逻辑,全部统一话处理了!!

 */


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值