安装
安装"react-router-dom": "^6.3.0"
npm install react-router-dom@6.3.0
创建路由表
src/router/index.tsx
以下的组件仅供参考, 可根据实际情况修改
import React from "react";
import {Navigate} from "react-router-dom";
import Home from "@/pages/Home";
import ProductList from "@/pages/ProductList";
import Catalogue from "@/pages/Catalogue";
import Projects from "@/pages/Projects";
import SingleProduct from "@/components/Products/SinglePage";
import Aboutus from "@/pages/Aboutus";
import Warhouse from "@/pages/Warhouse";
import LoginPage from "@/pages/LoginPage";
import Profile from "@/pages/Profile/index";
import MyProfile from "@/pages/Profile/MyProfile";
import WishList from "@/pages/Profile/WishList";
export interface RouteObject {
caseSensitive?: boolean;
children?: RouteObject[];
element?: React.ReactNode;
index?: boolean;
path?: string;
auth?: boolean;
}
const routes: RouteObject[] = [
{
path: '/catalogue',
element: <Catalogue/>,
auth: false
},
{
path: '/projects/:id?',
element: <Projects/>,
auth: false
},
{
path: '/productdetails/:id',
element: <SingleProduct/>,
auth: false
},
{
path: '/Category/:id',
element: <ProductList/>,
auth: false
},
{
path: '/aboutus',
element: <Aboutus/>,
auth: false
},
{
path: '/SalesOffice&Warehouse',
element: <Warhouse/>,
auth: false
},
{
path: '/login',
element: <LoginPage/>,
auth: false
},
{
path: '/profile',
element: <Profile/>,
auth: true,
children: [
{
path: 'myProfile',
element: <MyProfile/>,
auth: true
},
{
path: 'wishList',
element: <WishList/>,
auth: true
}
]
},
// 这个路由必须放在最后, 不然后续匹配路由时总会匹配到这个路由
{
path: '/',
element: <Home/>,
auth: false
},
]
export default routes
路由懒加载
文件内容基于src/router/index.tsx
修改
import React, {lazy, Suspense} from "react";
import {Navigate} from "react-router-dom";
const Home = lazy(() => import("@/pages/Home"));
const ProductList = lazy(() => import("@/pages/ProductList"));
const Catalogue = lazy(() => import("@/pages/Catalogue"));
const Projects = lazy(() => import("@/pages/Projects"));
const SingleProduct = lazy(() => import("@/components/Products/SinglePage"));
const Aboutus = lazy(() => import("@/pages/Aboutus"));
const Warhouse = lazy(() => import("@/pages/Warhouse"));
const LoginPage = lazy(() => import("@/pages/LoginPage"));
const Profile = lazy(() => import("@/pages/Profile/index"));
const MyProfile = lazy(() => import("@/pages/Profile/MyProfile"));
const WishList = lazy(() => import("@/pages/Profile/WishList"));
export interface RouteObject {
caseSensitive?: boolean;
children?: RouteObject[];
element?: React.ReactNode;
name?: string;
index?: boolean;
path?: string;
auth?: boolean;
}
// ...内容省略...
路由守卫
src/router/RouterGuard.tsx
import {message} from "antd";
import React, {useEffect} from "react";
import {useDispatch} from "react-redux";
import {logout} from "@/redux/modules/userSlice";
import {Location, NavigateFunction, useLocation, useNavigate, useRoutes} from "react-router-dom";
import {Dispatch, AnyAction} from "@reduxjs/toolkit";
interface RouteObject {
caseSensitive?: boolean;
children?: RouteObject[];
element?: React.ReactNode;
name?: string;
index?: boolean;
path?: string;
auth?: boolean;
}
// Recursively query the corresponding route
export function searchRouteDetail(path: string, routes: RouteObject[]): RouteObject | null {
for (const item of routes) {
// 当路由存在路由参数时,需要去掉路由参数后再进行匹配
// When the route has route parameters, you need to remove the route parameters before matching
if (item.path && item.path.includes(':')) {
const pathArr = item.path.split('/') || [item.path]
const pathArr2 = path.split('/') || [path]
// 如果路由参数个数相同,则进行匹配 / If the number of route parameters is the same, match
if (pathArr.length === pathArr2.length) {
// 将路由参数替换为实际的参数 / Replace route parameters with actual parameters
const pathArr3 = pathArr.map((item, index) => {
if (item.includes(':')) {
return pathArr2[index]
} else {
return item
}
})
// 实际路径 / actual path
const path2 = pathArr3.join('/')
// 如果实际路径和当前路由相同,且没有子路由,则返回当前路由
// If the actual path is the same as the current route and there is no child route, return the current route
if (path2.toLowerCase() === path.toLowerCase()) {
if (!item.children) {
return item
} else {
// 如果有子路由,则继续递归查询 / If there are child routes, continue to recursively query
const childPath = path.replace(item.path, '').replace('/', '')
return searchRouteDetail(childPath, item.children)
}
}
} else {
// 表示当前路由可能有参数 但是跳转的路径没有参数 此时可不匹配参数
// Indicates that the current route may have parameters but the path to jump does not have parameters, and the parameters can be ignored at this time
// 将路由参数去掉后再进行匹配
// Remove the route parameters before matching
const itemPathWithoutParam = item.path.split('/').filter((item) => !item.includes(':')).join('/')
if (path.startsWith(itemPathWithoutParam)) {
if (!item.children) {
return item
} else {
const childPath = path.replace(item.path, '').replace('/', '')
return searchRouteDetail(childPath, item.children)
}
}
}
} else if (item.path && path.startsWith(item.path)) {
// 此处表示当前路由没有参数,直接匹配即可
if (!item.children) {
return item
} else {
const childPath = path.replace(item.path, '').replace('/', '')
return searchRouteDetail(childPath, item.children)
}
}
}
return null
}
// 全局路由守卫
function guard(location: Location, navigate: NavigateFunction, dispatch: Dispatch<AnyAction>, routes: RouteObject[]) {
const {pathname} = location;
// 找到对应的路由信息
const routeDetail = searchRouteDetail(pathname, routes);
// 没有找到路由,跳转到指定页面
if (!routeDetail) {
// navigate("/");
return false;
}
// 如果需要权限验证
if (routeDetail.auth) {
const token = localStorage.getItem("token");
if (!token) {
dispatch(logout());
// 如果有其它message的弹窗,先关闭
message.destroy();
message.error("Please login first").then();
navigate('/login');
return false;
}
}
// 修改title / modify title
if (routeDetail.name) {
document.title = routeDetail.name
}
return true;
}
export const RouterGuard = (routes: RouteObject[]) => {
const location = useLocation();
const navigate = useNavigate();
const dispatch = useDispatch();
useEffect(() => {
guard(location, navigate, dispatch, routes);
}, [location, navigate, routes]);
return useRoutes(routes as any);
};
使用
src/App.tsx
import Header from "@/components/Header";
import MainFooter from "@/components/Footer/MainFooter";
import {RouterGuard} from "@/routes/RouterGuard"
import routes from "@/routes";
const App = () => {
return (
<>
<Header/>
{RouterGuard(routes)}
<MainFooter/>
</>
);
};
export default App;
src/index.tsx
如果使用了路由懒加载, 需要引入suspense, 否则不需要修改此文件
import React from "react";
import ReactDOM from "react-dom/client";
import { HashRouter as Router } from "react-router-dom";
import { Provider } from "react-redux";
// 若使用了路由懒加载, 需要引入suspense
import {Suspense} from "react";
import App from "./App";
ReactDOM.createRoot(document.getElementById("root")!).render(
<Provider store={store}>
<Router>
<Suspense fallback={<div>Loading...</div>}>
<App />
</Suspense>
</Router>
</Provider>
);