安装

安装"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>
);
Last Updated:
Contributors: huangdingxin, 黄定鑫