React基础笔记--5

React路由管理

react-router-dom

react-router-dom是PC端处理路由的解决方法,与之对应的还有移动端的路由适配处理。当代前端开发主要以SPA单页面开发为主。那么单页面和多页面的区别是什么?如图,其中SPA不适用于SEO优化,因为大部分SPA都是基于客户端渲染,也就是js动态向服务器获取数据渲染页面,这就会导致查看源代码的时候缺少很多信息,而SEO优化恰恰是需要从源代码中获取部分信息来优化。但是还有一种服务端渲染,在服务端处理的时候就将整个页面数据全部渲染完成直接返回给前端,而前端直接使用这个页面即可,在这个页面中包含了几乎所有的源代码信息,因此利于SEO优化。
在这里插入图片描述

哈希路由

哈希路由每一次路由跳转,都是改变当前页面的hash值,主页面是不会渲染,只针对部分内容进行局部刷新同时监听hashchange事件,渲染不同的内容。

模拟实现哈希路由,以下是一个静态结构,每一个a标签的href属性对应一个hash路由。每次点击的时候页面的路径会变化但是页面不会重新渲染。

  <nav class="nav-box">
    <a href="#/">首页</a>
    <a href="#/goods">商品</a>
    <a href="#/center">个人中心</a>
  </nav>
  <div class="view-box">
    <!-- 不同哈希值路由渲染不同的内容 -->
  </div>

在这里插入图片描述
搭建基本的路由匹配机制

    const viewBox = document.querySelector('.view-box')
    // 创建路由表,根据页面hash值的变化去路由表中匹配对应的内容渲染显示
    const routes = [
      {
        path: '/', //匹配的hash值
        component: '首页渲染' //渲染的组件或内容
      },
      {
        path: '/goods',
        component: '商品渲染'
      },
      {
        path: '/center',
        component: '个人渲染'
      }
    ]
    location.hash = '/' //默认打开hash地址:#/,即首页的路径

location.hash如果直接调用则获取的是当前页面的hash值,返回带有#符号的地址,location.hash=''如果采样这种形式则是赋值当前页面的hash值,设置的时候不用添加#,因为会默认在首部添加hash符号。

根据hash地址匹配不同的组件渲染

    function hashHandle() {
      let hash = location.hash.substring(1) //获取hash地址,#/url需要截取从第二个字符开始的路由进行匹配
      routes.forEach(item => {
        if (item.path === hash) {
          viewBox.innerHTML = item.component //渲染不同hash对应的组件
        }
      })
    }
    hashHandle() //默认执行一次
    window.addEventListener("hashchange", hashHandle) //hash地址改变的时候执行一次

history路由

和hash路由一致,也是路由更新,但是整个页面不会刷新。使用的是H5提供的新内置对象histroy,如图,这些方法和vue中路由的使用基本一致。location.pathname用于获取当前url路径,以/起头。
在这里插入图片描述
修改a标签的时候需要注意,直接这么写,标签进行跳转,会造成页面整个刷新,由于没有当前页面所以报错。
在这里插入图片描述

    const navBox = document.querySelector('.nav-box')
    function historyHandle() {
      let history = location.pathname
      routes.forEach(item => {
        if (item.path === history) {
          viewBox.innerHTML = item.component
        }
      })
    }

    history.pushState({}, '', '/') //手动跳转主页,顺序一定要在函数调用之前修改,否则路径不对
    historyHandle()

    navBox.addEventListener('click', e => {
      let target = e.target
      if (target.tagName === 'A') {
        e.preventDefault() //先阻止链接的默认跳转行为
        history.pushState({}, '', target.href) //获取当前标签的href属性
        historyHandle()
      }
    })

扩展:popstate事件当地址变化的时候会触发,调用 history.pushState() 或者 history.replaceState() 不会触发 popstate 事件

react-router-dom V5

安装第五版本:npm i react-router-dom@5.3.4
搭建如图所示的三个组件,通过路由渲染
在这里插入图片描述
在这里插入图片描述
react-router-dom中提供了需要用到的组件如:HashRouter,BrowserRouter,前者代表开始hash路由,后者代表开启浏览器history模式。这两个标签的使用规则都一致,将要渲染的内容整体包裹起来,且开启路由模式后,如果是HashRouter,默认会配置一个 #/哈希值。像RouteLink组件都需要在HashRouter,BrowserRouter两个路由模式中才能使用,否则会直接报错。
在这里插入图片描述
在代码中,如果还是基于a标签实现路由跳转,那么需要针对不同的路由模式设置不同的href值(如#)。因此react-router-dom提供了Link组件标签使用,该标签必须先开启路由模式才能使用,即外部已经有HashRouter,BrowserRouter组件处理。Link组件标签能够自动识别开启的路由模式,设置不同的模式,其底层是基于a标签实现的。
Link组件标签中通过设置to属性的值实现路由跳转,这里直接写具体路径即可,会自动设置不同路由模式需要的规则。

        <Link to="/">A</Link>
        <Link to='/b'>B</Link>
        <Link to='/c'>C</Link>
        代替下面的标签
        <a href="#/">A</a>
        <a href="#/b">B</a>
        <a href="#/c">C</a>

最终渲染如下
在这里插入图片描述
react-router-dom中引入Route 组件标签,该标签的作用是根据路径的变化,去匹配对应的值,显示不同的组件。path属性用于设置匹配的条件,component属性设置最终渲染的组件

      {/* 路由渲染组件放置位置 */}
      <div className="container">
        <Route path='/' component={A}></Route>
        <Route path='/b' component={B}></Route>
        <Route path='/c' component={C}></Route>
      </div>

效果图如下,发现可以实现跳转,但是页面显示有一点点问题,就是A组件还在,我们希望的效果是只显示对应的组件内容。
造成这样子的原因是我们目前的路由匹配是非精准匹配,且路由匹配成功一项后还会继续往后匹配。若页面当前路径为:/b,当第一个Routepath进行匹配的时候,/能够匹配成功/b这属于非精准匹配,于是就显示了A组件,然后继续向下匹配,第二个path/b,能够精准匹配页面的/b显示B组件。
在这里插入图片描述
在这里插入图片描述
我们首先解决上诉问题的匹配成功后,还会继续路由匹配的问题,该问题使用react-router-dom提供的Switch组件标签完成。该标签的作用是当一个路由匹配成功后便不会往下继续匹配。 如下代码,但是这样添加并没有解决精准匹配的问题,每次地址为/b,/c的时候都是匹配到/就结束匹配了,导致页面一直显示A组件。开启精准匹配给Route 组件标签exact属性,代表开启精准匹配

<Switch>
   <Route path='/' component={A}></Route>
   <Route path='/b' component={B}></Route>
   <Route path='/c' component={C}></Route>
</Switch>

开启精准匹配且匹配成功后不再继续匹配的完整代码。

        <Switch>
          <Route exact path='/' component={A}></Route>
          <Route exact path='/b' component={B}></Route>
          <Route exact path='/c' component={C}></Route>
        </Switch>

在这里插入图片描述
扩展:如果输入一个地址跟任何一个匹配规则都匹配不上的时候,需要显示404组件,可以在使用一个额外的Route 组件标签用于什么也没有匹配到的时候显示对应的组件内容。在该标签中path='*'或者path=''设置即可。但是该组件通常放在Switch组件标签的最后。

<Route path='*' component={Error}></Route>

或者没有匹配成功的时候,可以重定向到某一个组件显示内容。那么就需要使用react-router-dom'中的Redirect 重定向组件,该组件的格式:Redirect from='从哪来' to='重定向到哪' exact开启精准匹配
代码设置如下,Redirect 组件标签通常也是放在Switch组件标签的最后。当什么路由也没有匹配到的时候,会执行该语句,类似页面地址修改到/,会重新匹配一下路由规则进入<Route exact path='/' component={A}></Route>

<Redirect to='/'></Redirect>

Route组件中除了使用component属性控制渲染的组件,还可以通过render函数渲染组件,使用函数渲染组件是可以在返回组件之前做一些权限校验操作等。

          <Route exact path='/c' render={() => {
            let login = true
            if (login) {
              return <C></C>
            }
            return <Redirect to='/'></Redirect>
          }}></Route>

多级路由

修改之前的代码,然后创建二级路由组件

<HashRouter>
    <div className="App-box">
      <NavBox className="header">
        <Link to="/a">A</Link>
        <Link to='/b'>B</Link>
        <Link to='/c'>C</Link>
      </NavBox>
      <div className="container">
        <Switch>
          <Redirect exact from='/' to='/a'></Redirect>
          <Route path='/a' component={A}></Route> //二级路由的时候一级路由不能写eact精准匹配,否则无法进入组件进行二次路由匹配
          <Route path='/b' component={B}></Route>
          <Route path='/c' component={C}></Route>
          <Redirect to='/a'></Redirect>
        </Switch>
      </div>
    </div>
  </HashRouter>

二级路由组件,位于一级路由组件A下。
在这里插入图片描述
A组件中搭建二级路由,在二级路由中就不需要使用HashRouter等组件标签了,因为二级路由作用于一级路由内。

<div className="A-box">
    <div className="menu">
      <Link to='/a/a1' >a1</Link>
      <Link to='/a/a2' >a2</Link>
      <Link to='/a/a3' >a3</Link>
    </div>
    {/* 如果直接输入/a直接重定向到/a/a1,但是会和/a/a2等冲突所以启动精准匹配 */}
    <Switch>
      <Redirect exact from='/a' to='/a/a1'></Redirect>
      <Route path='/a/a1' component={A1}></Route>
      <Route path='/a/a2' component={A2}></Route>
      <Route path='/a/a3' component={A3}></Route>
      <Redirect to='/a/a1'></Redirect>
    </Switch>
  </div>

在这里插入图片描述
以上就是一个多级路由的代码,但是发生,当路由级数增多的时候,如果都将所有的路由组件或匹配规则写在组件中,那么代码就变的难以维护,且重复代码过多。如多个Route组件标签完全可以使用循环创建,而非手动。因此我们引入了react路由表管理机制

路由表管理机制

react的路由表管理机制需要自己手动搭建和创建规则,不像vue的路由已经是搭建好的模块,直接使用即可。

创建如图所示结构,index文件为主体路由,routes文件为路由表规则等
在这里插入图片描述
在routes文件中编写具体代码

// 创建路由表:为数组对象,对象中需要配置路由规则
/* 
  redirect:是否重定向,用于有<Redirect />的组件标签
  from:去哪个路径来
  to:去的目标地址
  exact:是否精准匹配
  path:匹配的路径 用于<Route /> 组件标签使用
  component:<Route/>组件匹配成功需要渲染的组件
  name:路由名称,类似vue的命名路由
  meta:路由元信息,自定义相关配置
  children:[]子路由
*/
import { A } from '@/views/A.jsx'
import { B } from '@/views/B.jsx'
import { C } from '@/views/C.jsx'
export const routes = [
  {
    redirect: true,
    exact: true,
    from: '/',
    to: '/a',
  },
  {
    path: '/a',
    name: 'a',
    component: A,
    meta: {},
    children: []
  },
  {
    path: '/b',
    name: 'b',
    component: B,
    meta: {},
  },
  {
    path: '/c',
    name: 'c',
    component: C
  }
]

本质是将如下代码转换为上面的规则描述。
在这里插入图片描述
在A组件中存在二级路由,在这里如果路由级数多,可以将children的配置信息封装一个文件(这里单独配置文件,在原先children后面直接配置报错,找不到原因。。。)
子路由声明children字段

import { A1 } from '@/views/A/A1.jsx'
import { A2 } from '@/views/A/A2.jsx'
import { A3 } from '@/views/A/A3.jsx'
export const childARoutes = [
  {
    redirect: true,
    exact: true,
    from: '/a',
    to: '/a/a1'
  },
  {
    path: '/a/a1',
    name: 'a-a1',
    meta: {},
    component: A1
  },
  {
    path: '/a/a2',
    name: 'a-a2',
    meta: {},
    component: A2
  },
  {
    path: '/a/a3',
    name: 'a-a3',
    meta: {},
    component: A3
  },
  {
    redirect: true,
    to: '/a/a1'
  }
]

在这里插入图片描述
routes文件中给children属性配置值

children: childARoutes

然后封装一个RouterVIew组件,页面中需要使用到路由跳转的地方都使用该组件完成

import { Switch, Route, Redirect } from 'react-router-dom'
// 基于属性将路由表传递给函数组件使用
export const RouterView = (props) => {
  let { routes } = props
  return <Switch>
    {
      routes.map((item, index) => {
        let { redirect, from, to, exact, path, component: Component } = item
        let config = {}
        // 是重定向组件就返回该组件
        if (redirect) {
          if (from) config.from = from
          if (to) config.to = to
          if (exact) config.exact = exact
          return <Redirect key={index} {...config}></Redirect>
        }
        if (path) config.path = path
        if (exact) config.exact = exact
        return <Route key={index} {...config} render={() => {
          // 使用render函数返回组件,可以在返回组件之前做特殊处理
          return <Component></Component> //component存放组件,但是组件首字母需要为大写,所以进行了重命名
        }}></Route>
      })
    }
  </Switch>
}

在App组件中引入使用,并传入路由表信息

import { RouterView } from './router'
import { routes } from './router/routes'
<RouterView routes={routes}></RouterView>

在A组件中引入使用,并传入二级路由表信息

import { RouterView } from '../router'
import { childARoutes } from '../router/childARoutes'
<RouterView routes={childARoutes}></RouterView>

页面可以正常切换
在这里插入图片描述

路由懒加载

在我们上面的代码中存在一个问题,我们在路由配置中,将所有用到的组件全部引入进来,这就会导致初次加载js文件的时候体积非常大,会有白屏,并且通常只需要先将首屏需要显示的组件内容展示即可。其他组件当路由匹配的时候动态加载即可。
在React中提供了lazy(callback)方法实现懒加载,该函数中传入一个回调函数并且返回一个import导入组件的操作。 这样子就可以在每次路由匹配的时候,加载单独的js文件显示。(分割打包,需要动态加载的路由组件都是一个js文件)
注意:使用这种lazy方法引入的组件建议是默认导出,不能是按需导出,否则报错
在按需导出的时候,可能会因为文件还没有加载完毕,但是在router文件中的index文件中已经return返回了,这就会导致页面报错。因此在最终返回的组件外部,需要使用react提供的Suspense 组件标签配合lazy()方法实现异步加载组件效果

将路由表中除了A组件是正常引入加载,其他均采样懒加载形式

import { lazy } from 'react'
component: lazy(() => import('../views/B.jsx')),
component: lazy(() => import('../views/C.jsx'))
component: lazy(() => import('../views/A/A1'))
component: lazy(() => import('../views/A/A2'))
component: lazy(() => import('../views/A/A3'))

Suspense 组件标签通常配合lazy方法成对使用

import { Suspense } from 'react'

        return <Route key={index} {...config} render={() => {
          // 使用render函数返回组件,可以在返回组件之前做特殊处理
          return <Suspense>
            <Component></Component>
          </Suspense>
        }}></Route>

初次加载,只把A跟A1组件加载使用,其余并未加载
在这里插入图片描述
当点击其他组件的时候,会按需加载,生成单独的js文件
在这里插入图片描述
已经加载过的资源不会重复获取
Suspense 组件还支持在异步组件未加载完毕前显示loading效果,该组件可以传入fallback配置属性使用,在该属性中,返回jsx视图。

<Suspense fallback={<>加载中。。。</>}>
            <Component></Component>
</Suspense>

在这里插入图片描述

扩展:如下资源获取,每次需要加载都会获取一个js文件,那么能不能将每个模块相关的放在一个js中获取,如和A有关的a1,a2,a3放在一个js文件中获取。
在这里插入图片描述
只需要给每一个a1,a2,a3的按需引入添加webpack标识,Webpack通过增加内联注释来告诉运行时,该有怎样的行为。。这样子在打包的时候,会将AChild有关的存放一个文件中,并且会将有关的文件一起引入打包处理。

component: lazy(() => import(/* webpackChunkName:"AChild" */'../views/A/A1'))
component: lazy(() => import(/* webpackChunkName:"AChild" */'../views/A/A2'))
component: lazy(() => import(/* webpackChunkName:"AChild" */'../views/A/A3'))

在这里插入图片描述

路由传参

基于Route组件标签实现路由匹配渲染的组件,会默认给组件传递三个属性:history,location,match

  • <Route path='/a' component={A} />基于特有属性component指定渲染组件,会默认传递这三个属性,组件能够基于props属性接收。
  • <Route path='/a' render={ (props)= { return <A {...props}/> } }/>基于特有属性render实现组件渲染,则会将默认参数传递给要实际渲染组件的函数作为参数使用,需要自己将传递属性传给组件。

因此在函数组件或类组件中,都可以基于props获取路由默认传递的参数

// 基于render接收属性并传递给组件接收使用
        return <Route key={index} {...config} render={(props) => {
          return <Suspense fallback={<>加载中。。。</>}>
            <Component {...props}></Component>
          </Suspense>
        }}></Route>

在函数组件中,可以使用props接收属性,也可以使用react-router-dom提供了hooks函数获取:useHistory, useLocation, useRouteMatch

在组件B中输出查看

import { useHistory, useLocation, useRouteMatch } from 'react-router-dom'
const B = (props) => {
  let history = useHistory()
  let location = useLocation()
  let match = useRouteMatch()
  console.log(history, location, match);
  console.log(props);
  return <div className="B-box">
    我是内容B
  </div>
}

在这里插入图片描述
其中history具体内容如下
在这里插入图片描述
location具体内容如下
在这里插入图片描述
match中具体内容如下
在这里插入图片描述

以上是基于Route路由组件渲染的组件,因此即可基于props获取参数,又可以使用hook函数获取参数。那么在非Route组件标签渲染的组件中能否获取这三个默认参数。

将几个Link组件标签提取封装在一个组件中。并导出使用,在该组件中使用两种方式查看输出值。

const HomeHeader = (props) => {
  const history = useHistory()
  console.log(history);
  console.log(props);
  return <NavBox className="header">
    <Link to="/a">A</Link>
    <Link to='/b'>B</Link>
    <Link to='/c'>C</Link>
  </NavBox>
}
export default HomeHeader

由此可见,只能使用hook函数获取这些路由默认传递的信息。
在这里插入图片描述
总结:在HashRouterBrowserRouter组件标签包裹的内部,非Route标签处理,普通渲染的组件可以使用react-router-dom提供的hook函数useHistory, useLocation, useRouteMatch获取默认传递的路由信息,但是无法经过props属性获取。如果是经过Route渲染的组件,那么可以使用props或者hook函数获取路由信息。

上面的总结,如果是用于函数组件,那么没有问题,如果应用于类组件,在HashRouterBrowserRouter,那么一个非Route渲染出来的组件,既不能使用props,也不能使用hook函数,该如何获取路由信息。处理方式:使用高阶组件

// 将上面的函数组件修改为类组件,想在非Route受控的组件中使用路由参数信息
class HomeHeader extends React.Component {
  render() {
    console.log(this.props); // 获取的是{}
    return <NavBox className="header">
      <Link to="/a">A</Link>
      <Link to='/b'>B</Link>
      <Link to='/c'>C</Link>
    </NavBox>
  }
}
const HomeHeaderProxy = (Component) => {
  return function HOC(props) { //实际使用该函数组件,因此组件名首字母要大写
    // 其他地方用到该组件,实际是使用HOC函数组件的内容,因此传递的参数会给被该函数接收
    // 在函数组件内部可以使用hook函数,获取参数,然后基于属性传递给实际渲染的类组件
    let histroy = useHistory()
    let location = useLocation()
    let match = useRouteMatch()
    return <Component {...props} histroy={histroy} location={location} match={match} />
  }
}
export default HomeHeaderProxy(HomeHeader)

App组件中调用
<HomeHeader name='navbar'></HomeHeader>

类组件中可以使用this.props获取
在这里插入图片描述
在v5版本中,提供了一个函数withRouter,该函数的功能就是实现上面高阶组件的功能。只需下面简短的两行就能实现上面高阶组件的功能。这样子,在一个非Route管理的组件中,就可以基于props获取路由信息。但是withRouter函数在V6版本的路由中删除了,因此在v6中,在一个非Route管理的组件中,如果一个类组件想获取,依旧需要手动实现高阶组件功能。

import { withRouter } from 'react-router-dom'
export default withRouter(HomeHeader)

有两种路由跳转方式,语法几乎一模一样

  1. Link标签实现路由跳转,同时,在to属性配置中可以直接写目标路由地址,也可以写配置对象,指定路由跳转时候携带的参数。在配置对象中,pathname指明了目标路由的地址,search指明了参数格式必须是urlencoded格式。replace 指明路由模式replace。默认push
    <Link to='/c'>
      <Button>点击跳转C</Button>
    </Link>
    <Link replace to={{
      pathname: '/c',
      search: ''
    }}>
      <Button>点击跳转C</Button>
    </Link>
  1. 编程式路由,使用js控制路由跳转。借助原生history对象或useHistoryhook函数
let history = useHistory()

    <Button onClick={() => {
      history.push('/c')
    }}>点击跳转C</Button>

search传参和使用(urlencoded)

两种方式的使用基本一致,那么传参该如何实现,这里以编程式路由为例子。
search配置项传递的是urlencoded参数格式。那么对应的传参如下

    <Button onClick={() => {
      history.push({
        pathname: '/c',
        search: `id=1&name=张三`
      })
    }}>点击跳转C</Button>

在这里插入图片描述
既然是这种传参方式,那么就可以直接问号拼接传参也可以

    <Button onClick={() => {
      history.push({
        pathname: '/c?id=1&name=张三',
      })
    }}>点击跳转C</Button>

在这里插入图片描述
这两种方法传递原理一样,都是将参数拼接在地址后面,但是这种传参方式缺点很明显,如果参数过多,则地址会变得很长,且直接将参数信息暴露出来,很不安全。优点是即使目标路由刷新了,但是这些参数依旧存在地址后面

那么基于search实现urlencoded传参,目标路由该如何获取传递的参数信息?
因为是采样search传参,所以参数信息是存放在location对象中保存,需要借助useLocationhook函数获取。

import { useLocation } from 'react-router-dom'
const C = () => {
  let location = useLocation()
  console.log(location);
  .....
}
export default C

在这里插入图片描述
如果想将search配置项中的参数取出组成一个对象,有如下两种常用方法

  • 借助qs库快速实现,但是需要注意,qs库是直接针对key=value&key=value这种格式进行转换,不会对?符号进行处理,因此需要先进行截取操作。
import qs from 'qs'
// C组件
  let location = useLocation()
  let search = qs.parse(location.search.substring(1))
  console.log(search);

在这里插入图片描述

  • 借助URLSearchParams构造函数快速创建一个URLSearchParams对象,需要在new实例化的时候传入参数信息进行初始化。在该对象身上,有许多操作对象属性的方法如delete,get,set等。其中可以使用get方法获取对应属性的值
  let search1 = new URLSearchParams(location.search.substring(1))
  console.log(search1);

在这里插入图片描述

  console.log(search1.get('id'), search1.get('name'));

在这里插入图片描述

路径传参(params)

路径传参,是将参数作为路径的一部分一起传递。在路由匹配中使用:占位符占位,因此需要先修改路由表中的路由。

  {
    path: '/c/:id/:name',
    name: 'c',
    component: lazy(() => import('../views/C.jsx'))
  }
<Button onClick={() => {history.push('/c/1/张三')}}>点击跳转C</Button>

在这里插入图片描述
path: '/c/:id/:name'如果是这种格式的路径,那么在跳转的时候每个占位符所需要的参数都必须传递,否则无法实现跳转
修改为 history.push('/c/1'),跳转如图,发现无法实现正常跳转功能了
在这里插入图片描述
因此我们可以在指定占位符的同时设置参数是否可选,使用?代表该参数可传项path: '/c/:id?/:name?',这样子在跳转的时候,/c/c/1,/c/1/张三均可以实现跳转功能。

因为是params格式,存放在match参数中,在目标组件中可以使用useRouteMatchhook函数获取,也可以直接使用useParams hook函数获取。

  let match = useRouteMatch()
  console.log(match);

在这里插入图片描述

  let params = useParams()
  console.log(params);

在这里插入图片描述
使用路径传参也是将参数放在url地址中,但是也存在不安全和参数过长问题。页面刷新路径参数信息也不会丢失

隐式传参

利用路径跳转的state参数实现,并在location中存储。不会出现在url地址中这种方式如果刷新页面,参数会丢失,需要重新获取

<Button onClick={() => {
	  history.push({
        pathname: '/c',
        state: {
          id: 1,
          name: '张三'
        }
      })
}}>点击跳转C</Button>
  let location = useLocation()
  console.log(location);

在这里插入图片描述
参数不会出现在地址中
在这里插入图片描述

NavLink

除了Link标签,还有NavLink标签,他们的具体使用语法一样,都是转换为a标签,唯一的区别就是NavLink标签会给当前路由添加一个默认激活样式类名。将当前页面的路由和to属性配置的字符串或pathname进行匹配。如果匹配成功,就给当前匹配成功的NavLink标签添加active类名,但是无任何样式。使用NavLink标签的好处是,可以给当前选中的路由添加样式激活信息。

在这里插入图片描述
在这里插入图片描述
可以使用activeClassName修改默认的样式类名active
在这里插入图片描述
设置激活时候路由导航对应的样式信息,这里使用的是默认提供的激活类名active

  a {
    ....
    &.active {
      color:red;
    }
  }

react-router-dom V6

首先安装第六版本的路由:npm i react-router-dom,第六版本和第五版大致思想一致,但是语法差异很大。

react-router-dom V6版本中,移除了Switch,Redirect,withRouter等。

在第六版本中,所以需要路由匹配规则的全部放在Routes组件标签中,可以理解为代替Switch组件标签。并且Redirect组件标签被Navigate组件替代。同时Route组件标签的语法也变化了。

Route组件标签中,使用element属性代替原有的componentrender属性。并且书写格式也有要求。如果按照如下书写格式,则会报错。

        <Routes>
          <Route path='/a' element={A}></Route>
          ....
        </Routes>

在这里插入图片描述

正确写法如下,将要渲染的组件,以完整的标签形式写全。
在如下代码中,Navigate 组件可以配合在Route 标签中使用,第一段代码用于匹配/,匹配成功由Navigate 标签实现重定向操作。
默认匹配成功,就不会再往下继续匹配,并且不在需要使用exact属性,默认开启精准匹配。

        <Routes>
          <Route path='/' element={<Navigate to='/a'></Navigate>}></Route>
          <Route path='/a' element={<A></A>}></Route>
          <Route path='/b' element={<B></B>}></Route>
          <Route path='/c' element={<C></C>}></Route>
          <Route path='*' element={<Navigate to='/a'></Navigate>}></Route>
        </Routes>

Navigate to属性不仅可以直接写跳转的地址,也可以写一个配置对象和路由跳转方式。

<Route path='*' element={<Navigate replace to={{
            pathname: '/a',
            search: '?from=404' 
          }}></Navigate>}></Route>
</Routes>

多级路由

在V6版本中,如果想实现多级路由,不再像V5一样,将路由分散到各个组件中编写。而是放在一起统一处理。

如下代码是继续在原有路由中继续添加新路由,在目标<A></A>一级路由组件中,需要使用提供的Outlet组件标签,告诉二级路由放置渲染的位置即可。

        <Routes>
          <Route path='/' element={<Navigate to='/a'></Navigate>}></Route>
          <Route path='/a' element={<A></A>}>
            // 二级路由
            <Route path='/a' element={<Navigate to='/a/a1'></Navigate>}></Route>
            <Route path='/a/a1' element={<A1></A1>}></Route>
            <Route path='/a/a2' element={<A2></A2>}></Route>
            <Route path='/a/a3' element={<A3></A3>} ></Route>
            <Route path='*' element={<Navigate to='/a/a1'></Navigate>}></Route>
          </Route>
          <Route path='/b' element={<B></B>}></Route>
          <Route path='/c' element={<C></C>}></Route>
          <Route path='*' element={<Navigate replace to={{
            pathname: '/a',
            search: '?from=404'
          }}></Navigate>}></Route>
        </Routes>
import { NavLink, Outlet } from 'react-router-dom'
const A = () => {
  return <div className="A-box">
    <div className="menu">
      <NavLink to='/a/a1'>a1</NavLink>
      <NavLink to='/a/a2'>a2</NavLink>
      <NavLink to='/a/a3'>a3</NavLink>
    </div>
    {/* 存放容器 */}
    <Outlet></Outlet>
  </div>
}

在这里插入图片描述

路由跳转及传参

在V6版本中,取消了所有基于props传递默认的参数。即使是被Route匹配路由渲染的也不会使用props传递参数。统一采样了hook函数。如果不是被HashRouterBrowserRouter控制的组件,无法使用路由提供的hook函数,一旦强行使用就会报错。

B组件由Route组件标签渲染出来,想使用props获取默认参数失败,为空对象
在这里插入图片描述
APP不在HashRouter路由组件的控制内,不能直接使用路由中的hook函数,否则报错
在这里插入图片描述

在函数组件中统一采样hook函数,那么完全可以获取路由中的参数,但是在类组件中就无法处理。既没有属性传参,也没有hook函数,因此需要自己处理。可以在构建路由表的时候,会基于Route匹配渲染组件的同时,基于属性将路由信息传递。并且在非Route匹配渲染的类组件中,手写一个withRouter方法实现高阶组件的功能,将默认的参数传递给组件使用。

在V6版本中如果想实现路由跳转有以下三种方式

  • 基于LinkNavLink组件标签实现路由跳转,需要手动点击触发。
  • 基于Navigate组件标签的to 属性,实现自动跳转。
<Navigate to='/c'></Navigate>
  • (编程式路由导航)基于useNavigatehook函数实现路由跳转。在v6版本中,取消了useHistory函数,同时对应着history对象也取消了。useNavigate函数执行的返回结果也是一个函数。该返回的函数通过设置参数实现路由跳转。
  let navigate = useNavigate()
  console.log(navigate);

在这里插入图片描述

navigate({
        pathname: '/c',
        search: '?id:1&name:张三'
})
navigate('/c')
navigate('/c',{replace:true})

search传参

let navigate = useNavigate()
.....
<Button onClick={() => {
      navigate({
        pathname: '/c',
        search: '?id:1&name:张三'
      })
}}>跳转到C组件</Button>
或者使用qs库,将对象转换为urlencoded格式传递
search: qs.stringify({
       id: 1,
       name: '张三'
})

在这里插入图片描述
在C组件中可以基于useLocation函数获取对象中的search值。

  let search = qs.parse(useLocation().search.substring(1))
  console.log(search);

在这里插入图片描述
也可以基于URLSearchParams构造函数获取。

  let search = new URLSearchParams(useLocation().search)
  console.log(search.get('id'), search.get('name'));

在这里插入图片描述
不过在v6版本中提供了useSearchParams函数。该函数调用会返回一个数组,数组的第一个元素就是基于URLSearchParams构造函数返回的,使用格式上上面一致

console.log(useSearchParams());
let [search] = useSearchParams()
console.log(search.get('id'), search.get('name'));

在这里插入图片描述
在这里插入图片描述

路径传参

<Route path='/c/:id?/:name?' element={<C></C>}></Route>
let navigate = useNavigate()
<Button onClick={() => {navigate('/c/1/张三')}}>跳转到C组件</Button>

在C组件中依旧使用useParams函数接收。同时移除了useRouterMatch函数,因此无法基于该函数获取对象中的params参数信息。

  let params = useParams()
  console.log(params);

在这里插入图片描述
在这里插入图片描述

隐式传参

在使用useNavigate函数实现路由隐式传参跳转,设置state属性的位置和v5中的useHistory的位置不同
在这里插入图片描述
在接口定义中指出了state应该书写的位置
在这里插入图片描述

navigate('/c', { state: { id: 1, name: '张三' } })navigate({ pathname: '/c' }, { state: { id: 1, name: '张三' } })

在C组件中基于useLocation函数获取。和v5不同,路径传参不会随页面刷新而消失,但是隐式传参会。但是在v6中,隐式传参即使刷新也不会丢失。

  let state = useLocation()
  console.log(state);

在这里插入图片描述

路由表统一管理

首先封装路由表

import { Navigate } from "react-router-dom"
import { lazy } from "react"
import A from '@/views/A.jsx'

const aRoutes = [
  {
    path: '/a',
    component: () => <Navigate to='/a/a1'></Navigate>
  },
  {
    path: '/a/a1',
    component: lazy(() => import(/*webpackChunkName:"AChild"*/'@/views/A/A1.jsx'))
  },
  {
    path: '/a/a2',
    component: lazy(() => import(/*webpackChunkName:"AChild"*/'@/views/A/A2.jsx'))
  },
  {
    path: '/a/a3',
    component: lazy(() => import(/*webpackChunkName:"AChild"*/'@/views/A/A3.jsx'))
  },
  {
    path: '*',
    component: () => <Navigate to='/a/a1'></Navigate>
  }
]

const routes = [
  {
    path: '/',
    //不能直接返回组件,否则已导入的时候就会执行跳转,并且这里也需要设置to跳转,这样子设置方便
    // <Route path='/a' element={<Navigate to='/a/a1'></Navigate>}></Route> 如果直接放置组件,会直接跳转
    component: () => <Navigate to='/a'></Navigate>
  },
  {
    path: '/a',
    component: A,
    children: aRoutes
  },
  {
    path: '/b',
    component: lazy(() => import('@/views/B.jsx'))
  },
  {
    path: '/c',
    component: lazy(() => import('@/views/C.jsx'))
  },
  {
    path: '*',
    component: () => {
      return <Navigate replace to={{
        pathname: '/a',
        search: '?from=404'
      }}></Navigate>
    }
  }
]

export default routes

在index中的代码,负责实现创建Route组件标签,利用递归实现,并且最终创建一个路由容器。

import { Routes, Route, useLocation, useNavigate, useParams } from 'react-router-dom'
import routes from '@/router/routes.js'
import { Suspense } from 'react'
/* 封装组件的用处,因为v6版本取消了props默认传参,全部采样hook传参
如果是类组件,并且是经过Route组件渲染,就会造成既不能使用props传参
也不能使用hook函数。因此封装一个函数,可以在返回最终组件前,执行某些操作
如实现props传参,路由验证登 */
const Element = (props) => {
  let { component: Component } = props
  let location = useLocation()
  let params = useParams()
  let navigate = useNavigate()
  return <Component location={location} params={params} navigate={navigate}></Component>
}

// 创建所有的Route
const createRoute = (routes) => {
  return <>
    {/* 渲染实际的Route组件 */}
    {
      routes.map((item, index) => {
        let { path, children } = item
        /* elementp配置不直接渲染组件,二次封装组件,在该组件中返回要渲染的组件 */
        return <Route key={index} path={path} element={<Element {...item}></Element>}>
          {/* 如果存在子路由,就递归创建Route */}
          {Array.isArray(children) ? createRoute(children) : null}
        </Route>
      })
    }
  </>
}

// 创建容器,存放Route组件
const RouterView = () => {
  // 因为使用了懒加载,所以会出现异步加载的情况,需要使用异步处理组件
  return <Suspense fallback={<>加载中。。。</>}>
    <Routes>
      {createRoute(routes)}
    </Routes>
  </Suspense>
}

export default RouterView

// 实现withRouter,用于非Route渲染的类组件,也可以获取路由信息
// 一个项目只能一个默认导出,因此使用按需导出
export const withRouter = (Component) => {
  return function HOC(props) {
    let location = useLocation()
    let params = useParams()
    let navigate = useNavigate()
    return <Component {...props} location={location} params={params} navigate={navigate}></Component>
  }
}

扩展: [CreateRoute] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>代表在 <Routes>{createRoute(routes)}</Routes>Routes组件中只能直接存在Route组件,不能是其他的自己使用的组件。

这样子在页面中可以正常实现原有功能。并且所有基于Route路由匹配渲染的组件(无论函数组件或类组件),都可以使用props获取参数。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

useRoutes

上面代码如果不想自己手动实现,那么可以基于useRoutes函数实现,基本逻辑就递归创建Route标签使用,不过这样子创建的话就没有我们自己的功能丰富,无法传递props,给类组件添加一些功能。该函数完全为函数组件所使用的。

<HashRouter><App></App></HashRouter>

使用useRoutes函数的时候,必须被HashRouter控制处理。 提前引入使用,注意在element处,必须写组件,如果是懒加载之类引入的组件,必须再次使用一个变量接收作为组件使用

import { useRoutes, Navigate } from 'react-router-dom'
import HomeHeader from '@/components/HomeHeader.jsx'
export const App = () => {
  const LazyA1 = lazy(() => import('@/views/A/A1.jsx'))
  // useRoutes函数返回的是一个虚拟DOM
  let element = useRoutes([
    {
      path: '/',
      element: <Navigate to='/a'></Navigate>
    },
    {
      path: '/a',
      element: <A></A>,
      children: [
        {
          path: '/a',
          element: <Navigate to='/a/a1'></Navigate>
        },
        {
          path: '/a/a1',
          element: <LazyA1></LazyA1>
        }
      ]
    },
    {
      path: '/b',
      element: <B></B>
    },
    {
      path: '/c',
      element: <C></C>
    }
  ])
  return <>
    <HomeHeader></HomeHeader>
    <Suspense> {element}</Suspense>
  </>
}

页面可以正常显示
在这里插入图片描述

useReducer

useReducer是对useState的升级处理。执行useReducer函数会返回一个状态值和dispatch函数。useReducer函数执行大致和redux思想一致,都是需要基于reducer函数实现具体功能。useReducer函数会创建一个局部状态管理。

const [state, dispatch] = useReducer(reducer, initialValue)
import { useReducer } from "react"
let initialValue = {
  num: 0
}
const reducer = (state, action) => {
  state = { ...state }
  switch (action.type) {
    case '+':
      state.num++;
      break;
    case '-':
      state.num--;
      break;
  }
  return state
}
const A1 = () => {
  const [state, dispatch] = useReducer(reducer, initialValue)
  return <div className="A1">
    我是A1组件
    <hr />
    <h4>我是num的值:{state.num}</h4>
    <Button onClick={() => dispatch({ type: '+' })}></Button>
    <Button onClick={() => dispatch({ type: '-' })}></Button>
  </div>
}

在这里插入图片描述
那么什么情况下需要使用useReducer,如果单个组件中需要用到的状态值很多的情况下,可以使用useReducer创建并统一集中管理。这个情况下如果使用useState创建的话,会执行很多重复的代码。使用useReducer管理多个状态的时候,如果想修改某一个状态值可以直接修改,无需像useState一样还需要注意是否是状态整体替换之类的(一个对象中多个状态值,想修改某一个的情况)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值