demo-react-router:react router 路由学习笔记,适合 react-router v6 和 v7 版本
1. 代码仓库
2. 主线任务
2.1. 依赖下载
pnpm add react-router@7 -S
// or
pnpm add react-router@6 -S
2.2. 配置式路由
1、目录结构如下:
2、router/index.tsx:
import React from 'react';
import { RouterProvider } from 'react-router/dom';
import { Navigate, createHashRouter, Outlet } from 'react-router';
// config
import { MenulevelConfig } from './modules/menulevel';
import { PUBLIC_ROUTE, ERROR_ROUTE, NO_MATCHED_ROUTE } from './sys';
export const Router: React.RC = () => {
// 受保护的路由(业务路由)
const PROTECTED_ROUTE = {
path: '/',
element: (
<>
<Outlet />
</>
),
children: [
// 默认的重定向
{ index: true, element: <Navigate to={'/403'} replace /> },
// 业务模块
MenulevelConfig,
],
};
const routes = [
PUBLIC_ROUTE, // 公共路由
PROTECTED_ROUTE, // 受保护的路由(业务路由)
ERROR_ROUTE, // 错误路由
NO_MATCHED_ROUTE, // 上面都没有匹配到的路由,放最后
];
const router = createHashRouter(routes);
return <RouterProvider router={router} />;
};
export default Router;
3、router/modules/menulevel:
import { Suspense } from 'react';
import { Outlet, Navigate } from 'react-router';
export const MenulevelConfig = {
order: 2,
path: 'menu_level',
meta: {},
element: (
<Suspense fallback={<div>loading...</div>}>
<Outlet />
</Suspense>
),
children: [
{
path: 'menu_level_1a',
element: <div>lervel 1a</div>,
},
{
path: 'menu_level_1b',
children: [
{
index: true,
element: <Navigate to='menu_level_2a' replace />,
},
{
path: 'menu_level_2a',
element: <div>lervel 2</div>,
},
{
path: 'menu_level_2b',
element: <div>lervel 2</div>,
},
],
},
],
};
export default MenulevelConfig;
4、router/sys/index.ts:
// index.ts
export { ERROR_ROUTE } from './error-routes';
export { PUBLIC_ROUTE } from './public';
export { NO_MATCHED_ROUTE } from './no-matched';
// error-routes.tsx
import { Suspense, lazy } from 'react';
import { Outlet } from 'react-router';
const Page403 = lazy(() => import('@/pages/sys/error/Page403'));
const Page404 = lazy(() => import('@/pages/sys/error/Page404'));
const Page500 = lazy(() => import('@/pages/sys/error/Page500'));
export const ERROR_ROUTE = {
element: (
<div>
<Suspense fallback={<div>loading...</div>}>
<Outlet />
</Suspense>
</div>
),
children: [
{ path: '403', element: <Page403 /> },
{ path: '404', element: <Page404 /> },
{ path: '500', element: <Page500 /> },
],
};
// public.tsx
import Login from '@/pages/sys/login';
export const PUBLIC_ROUTE = {
path: '/login',
element: <Login />,
};
// no-matched.tsx
import { Navigate } from 'react-router';
export const NO_MATCHED_ROUTE = {
path: '*',
element: <Navigate to='/404' replace />,
};
5、App.tsx:
import Router from './router';
export default function App() {
return (
<>
<Router />
</>
);
}
6、访问 http://localhost:5173/#/404
3. 支线任务
3.1. 关于路由鉴权
1、router/components/ProtectedRoute.tsx:
import { Navigate } from 'react-router';
const ProtectedRoute = ({ children }) => {
// TODO: 鉴权逻辑,当前演示是从本地存储中获取token
const token = localStorage.getItem('token') || false;
console.log(token,"===")
if (!token) {
return <Navigate to='/login' replace />; // 如果没有token,跳转到登录页面
}
return <>{children}</>;
};
export default ProtectedRoute;
2、router/index.tsx:
export const Router: React.RC = () => {
// 受保护的路由(业务路由)
const PROTECTED_ROUTE = {
path: '/',
element: (
<ProtectedRoute>
<>
<Outlet />
</>
</ProtectedRoute>
),
};
}
export default Router;
3.2. layouts 布局
1、由于layouts 不在当前文章中,所以组件先只做简单的处理,layouts/index.tsx
import { Outlet } from 'react-router';
const DashboardLayout = () => {
// layouts的内容省略...
return <><Outlet /></>;
};
export default DashboardLayout;
2、router/index.tsx:
import DashboardLayout from '@/layouts';
import ProtectedRoute from './components/ProtectedRoute';
export const Router: React.RC = () => {
// 受保护的路由(业务路由)
const PROTECTED_ROUTE = {
path: '/',
element: (
<ProtectedRoute>
<DashboardLayout />
</ProtectedRoute>
),
};
};
export default Router;
3.3. 基于 react-error-boundary 异常页面捕获
1、当页面发生错误时:
以下代码尝试直接使用了未定义的abc
import { Suspense } from 'react';
import { Outlet, Navigate } from 'react-router';
function LevelPage({ children }){
// 这里的 abc 未定义 会报错
return <div>{ abc }{ children }</div>
}
export const MenulevelConfig = {
order: 2,
path: 'menu_level',
meta: {},
element: (
<Suspense fallback={<div>loading...</div>}>
<Outlet />
</Suspense>
),
children: [
{
path: 'menu_level_1a',
element: <LevelPage>lervel 1a</LevelPage>,
},
],
};
export default MenulevelConfig;
2、React 抛出异常,为了不影响用户
3、借助 react-error-boundary
React 应用里,一旦某个组件在渲染、生命周期方法或者构造函数中抛出未捕获的错误,整个应用可能会崩溃,导致页面白屏,给用户带来糟糕的体验
pnpm add react-error-boundary -S
4、修改 router/components/ProtectedRoute.tsx:
import { Navigate } from 'react-router';
import { ErrorBoundary } from "react-error-boundary";
import PageError from "@/pages/sys/error/PageError";
const ProtectedRoute = ({ children }) => {
// ...其他代码
return <ErrorBoundary FallbackComponent={PageError}>{children}</ErrorBoundary>;
};
export default ProtectedRoute;
5、pages/sys/error/PageError
const PageError = () => {
return (
<div>
<h1>我是捕获的错误页面:PageError</h1>
</div>
);
};
export default PageError;
6、访问:http://localhost:5173/#/menu_level/menu_level_1a
错误被 react-error-boundary 捕获了
3.4. DOM 嵌套方式
1、App.tsx
import { Suspense } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router';
export default function App() {
return (
<Router>
<Suspense fallback={<div>loading...</div>}>
<Routes>
<Route path='/' element={<div>home</div>} />
<Route path='/about' element={<div>about</div>} />
</Routes>
</Suspense>
</Router>
);
}
3.5. 路由懒加载
使用 React.lazy + Suspense 实现路由级代码分割
1、App.tsx
import { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router';
// 使用动态导入实现懒加载
const Home = lazy(() => import('./pages/home'));
const Login = lazy(() => import('./pages/sys/login'));
export default function App() {
return (
<Router>
<Suspense fallback={<div>loading...</div>}>
<Routes>
<Route path='/' element={<Home />} />
<Route path='/login' element={<Login />}></Route>
<Route path='/about' element={<div>about</div>} />
</Routes>
</Suspense>
</Router>
);
}
3.6. LazyImportComponent 组件
import { Suspense, LazyExoticComponent } from "react";
interface ILazyImportComponent {
children: LazyExoticComponent<() => JSX.Element>;
}
const LazyImportComponent = ({ children }: ILazyImportComponent) => {
return (
<Suspense fallback={<div>Loading...</div>}>
<children />
</Suspense>
);
};
1、main.tsx
import ReactDOM from "react-dom/client";
import { lazy } from "react";
import {createHashRouter, RouterProvider} from "react-router";
function Router() {
const router = createHashRouter([
{
path: "/login",
element: (
<LazyImportComponent
children={lazy(() => import("@/pages/Login/index.tsx"))}
/>
),
},
]);
return <RouterProvider router={router} />;
}
ReactDOM.createRoot(document.getElementById("root")!).render( <Router />);