React 路由匹配的问题
目录
- 最近在写 React 项目时,被 React 路由匹配的问题折磨的够呛,这里来分享一下其中的一些坑,以及我的路由配置组件。
前提
安装路由
sh
npm i react-router-dom -S
- 用 react-router-dom 不用 react-router 的原因:
- react-router-dom 多出
<Link>
<BrowserRouter>
等组件。 - 更适合,更好用,更简单(没有这些组件就需要你自己去封装)。
- react-router-dom 多出
配置 history 模式
- 如果使用 hash 模式可以忽略此步骤。
- 页面使用:
jsx
import { BrowserRouter } from 'react-router-dom'
// 路由组件
export default function RouterView() {
return <BrowserRouter></BrowserRouter>
}
- 404 配置
- history 模式下,网站刷新会 404,。
- webpack.config.js 配置,配置详情请看 publicPath 和 historyApiFallback
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上
}
}
}
~/
表示自定义alias
即path.resolve(__dirname, "src/views")