Skip to content

React 路由匹配的问题

目录

  • 最近在写 React 项目时,被 React 路由匹配的问题折磨的够呛,这里来分享一下其中的一些坑,以及我的路由配置组件

在这里插入图片描述

前提

安装路由

sh
npm i react-router-dom -S
  • 用 react-router-dom 不用 react-router 的原因:
    • react-router-dom 多出 <Link> <BrowserRouter> 等组件。
    • 更适合,更好用,更简单(没有这些组件就需要你自己去封装)。

配置 history 模式

  • 如果使用 hash 模式可以忽略此步骤。
  • 页面使用:
jsx
import { BrowserRouter } from 'react-router-dom'
// 路由组件
export default function RouterView() {
  return <BrowserRouter></BrowserRouter>
}
js
{
  output: {
    // history会返回真实的路径
    // 当你使用按需加载的时候,默认为相对路径则会出现404
    publicPath: "/"
  }
  devServer:{
    historyApiFallback: true // 解决路由 history 404 问题
  }
}
  • 上线服务器 Nginx 部署 history 模式 404,配置详情
ssh-config
server {
  location / {
    try_files $uri /index.html
  }
}

React 路由匹配规则

  • 这里提一下 Vue 路由匹配规则:
    • 调用了匹配器的 match 方法,通过 path-to-regexp , 来创建一个正则表达式 , 然后 , 通过这个正则来检查是否匹配。
    • 如果已经匹配则不会往下再去匹配。
  • React 匹配规则:
    • 从上到下执行,会继续匹配所以满足匹配的项。

/路由地址会匹配所有

  • 如果我现在的路由是 /admin,则会显示下面两个组件。
    • 因为 /admin 会匹配 //admin 的前面有 /
jsx
<Route path="/" component={} />
<Route path="/admin" component={} />
  • 解决:让他精确匹配,exact 属性表示要完全相等。
    • /admin/ 是不全等的,所以不会同时出现两个。
jsx
<Route path="/" exact component={} />
<Route path="/admin" component={} />

路由匹配多个

  • 路由从上往下匹配,先匹配了 /admin,他还会继续匹配 /
  • 上面可以添加 exact 精确匹配可以达到目的。
jsx
<Route path="/admin" component={} />
<Route path="/" component={} />
  • 解决:
    • 可以使用上面的 exact 属性精确匹配。
    • 这里用另外一种方法。<Switch> 组件匹配完一个之后不再继续往下匹配。
jsx
<Switch>
  <Route path="/admin" component={} />
  <Route path="/" component={} />
</Switch>

完整路由组件

  • 注意:因为我的第一个子路由和父路由不一样,所以不需要用到 exact 精确匹配。
  • 如果你的第一个子路由和父路由一样,则需要将第一个子路由上加上 exact
    • /admin 路由组件放前面的原因是因为如果他匹配了 / 则不会往下匹配,因为 <Switch> 组件,所以 / 路由要放在 Routes 数组的最后一个对象即可。
点击查看代码
jsx
import React from 'react'
import { BrowserRouter, Switch, Route, Redirect } from 'react-router-dom'
import { Require } from '@/utils/global/function'

// 路由配置
const Routes = [
  {
    path: '/admin', // 账号管理模块
    children: [
      {
        path: '/admin/login' // 登录页面
      },
      {
        path: '/admin/forget' // 忘记密码页面
      }
    ]
  },
  {
    path: '/', // 首页模块
    children: [
      {
        path: '/home' // 首页-单页
      },
      {
        path: '/public', // 公共模块
        children: [
          {
            path: '/public/noticeList' // 公告列表
          },
          {
            path: '/public/helpCenter' // 帮助中心
          },
          {
            path: '/public/versionUpdate' // 版本更新
          }
        ]
      },
      {
        path: '/function', // 功能模块
        children: [
          {
            path: '/function/imgList' // 图片列表
          }
        ]
      },
      {
        path: '/imgText', // 图文模块
        children: [
          {
            path: '/imgText/carouselList' // 轮播图列表
          },
          {
            path: '/imgText/specialList' // 特效列表
          },
          {
            path: '/imgText/articleList' // 文章列表
          }
        ]
      },
      {
        path: '/user', // 用户中心模块
        children: [
          {
            path: '/user/userInfo' // 用户信息
          },
          {
            path: '/user/userAdmin' // 用户管理
          },
          {
            path: '/user/editPwd' // 修改密码
          },
          {
            path: '/user/feedbackList' // 用户反馈
          }
        ]
      }
    ]
  }
]

// 给路由添加组件
;(function addRouter(arr) {
  arr.forEach(elem => {
    const path = elem.path
    let url = ''
    // 首页-单页自定路径
    switch (path) {
      case '/':
        url = '/index'
        break
      case '/home':
        url = '/index/home'
        break
      default:
        url = path
        break
    }
    elem.component = Require(() => import(`~/${url.slice(1)}`)) // 懒加载组件
    // 判断是否有子路由
    if (elem.children) {
      addRouter(elem.children) // 子集添加组件
    }
  })
})(Routes)

// 路由组件
export default function RouterView() {
  return (
    <BrowserRouter>
      {(function routerComponent(arr) {
        return (
          <Switch>
            {/* 路由声明组件 */}
            {arr.map(({ path, children, component: Comp }, i) => (
              <Route
                path={path}
                key={i}
                children={props => <Comp {...props}>{children && routerComponent(children)}</Comp>}
              ></Route>
            ))}
            <Redirect path="*" to={arr[0].path}></Redirect>
          </Switch>
        )
      })(Routes)}
    </BrowserRouter>
  )
}
  • 懒加载方法 Require
js
import React, { Component } from 'react'
/**
 * 组件懒加载
 * @param {Any} importComponent  - 需要导入的组件
 * @returns {Any} - 导出组件
 */

export const Require = importComponent => {
  return class extends Component {
    state = {
      component: null
    }
    componentDidMount() {
      importComponent() //传进来的函数返回我们想要的组件
        .then(cmp => {
          //这里的cmp估计就是组件的返回值,类似promise的resolve写法
          this.setState({ component: cmp.default }) //把组件存起来
        })
    }
    render() {
      const C = this.state.component // 渲染的时候把组件 B 拿出来
      return C ? <C {...this.props} /> : null // 返回的其实就是组件 B,并且把传给A的属性也转移到B上
    }
  }
}
  • ~/ 表示自定义 aliaspath.resolve(__dirname, "src/views")

基于 MIT 许可发布