Back to blog
May 17, 2024
3 min read

梳理当前项目的路由系统(一),概念梳理

梳理一个完整项目的整体路由系统是相对复杂的,我们将逐步深入了解,首先进行一些概念梳理

从路由系统开始 router/index.tsx,首先分析依赖项

import { lazy } from 'react';
import { Navigate, RouteObject, RouterProvider, createHashRouter } from 'react-router-dom';

import { lazy } from 'react';

lazy 是 React 提供的一个函数,用于懒加载组件。 通过懒加载,可以优化大型应用的性能,避免一次性加载所有组件。 主要解决:减少初始加载时间

import { Navigate, RouteObject, RouterProvider, createHashRouter } from 'react-router-dom';

Navigate:实现重定向功能,在路由变化时导航到另一个路径。例如:需要在某些条件下进行页面重定向,如在用户登录后重定向到首页,或在未找到页面时重定向到 404 页面。 RouteObject:定义路由对象的类型,用于描述路由结构。帮助开发者更清晰地描述和管理路由结构。 RouterProvider:提供路由上下文,通常在应用的根组件中使用,提供所有路由相关的上下文。使应用中所有组件都可以使用路由功能。 createHashRouter:创建一个 Hash 路由器,使用 URL 哈希(#)部分进行路由管理。简化路由管理,适用于需要支持不依赖服务器配置的前端路由应用。

RouteObject 类型包含很多功能,定义路由对象的结构,包含路径、组件、子路由等等,例如

  1. path:路由路径,字符串类型
  2. element:要渲染的React组件
  3. children:自路由,类型也是 RouteObject 数组
const routes: RouteObject[] = [
  {
    path: "/",
    element: <HomePage />,
    children: [
      {
        path: "about",
        element: <AboutPage />,
      },
      {
        path: "contact",
        element: <ContactPage />,
      },
    ],
  },
];

RouterProvider:是 react-router-dom 提供的一个组件,用于提供路由上下文,通常包裹在根组件中,用于将路路由上下文提供给整个应用。在React应用中,使用 RouterProvider 提供路由上下文是一种标准方法,是当前社区的最佳实践。

createHashRouter: react-router-dom 提供的一个函数,用于创建 Hash 路由器。使用 URL 中的哈希(#)部分进行路由管理,例如 http://example.com/#/home。

项目中的组件和自定义Hook

import DashboardLayout from '@/layouts/dashboard';
import AuthGuard from '@/router/components/auth-guard';
import { usePermissionRoutes } from '@/router/hooks';
import { ErrorRoutes } from '@/router/routes/error-routes';

DashboardLayout:布局组件,定义应用的整体布局结构。 1. 负责定义应用的整体布局,包括导航栏、侧边栏、页脚和主内容区域。提供一致的用户界面,并渲染具体的页面内容。 2. 在大多数应用中,当前布局组件可以被认为是应用组件的入口,它管理和渲染应用的主要界面。 AuthGuard:路由守卫组件,用于保护特定路由,只有经过认证的用户才能访问。 1. 负责检查当前用户是否有权限访问某个路由,通常这包括用户是否登录是否具备访问权限等等。 2. 通常来说,如果用户不满足访问条件,当前组可以将用户重定向到登录页面。 3. 主要职责:处理没有登录的用户,检查用户是否已登录,如果用户没有登录,当他们试图访问受保护的路由时,重定向到登录页 4. 用户保护整个路由或大的路由组,确保只有已经登录的用户才能访问这些页面。 5. 例如:一个未登录的用户试图直接访问 /dashboard 页面,AuthGuard 会检测到用户未登录,并将其重定向到 /login 页面。 usePermissionRoutes:自定义 Hook,用于动态生成基于用户权限的路由。 1. permission 允许,许可 2. 主要职责:处理每个登录用户具体能看到的路由、页面或组件的展示情况。 3. 根据用户的权限动态生成路由列表,确保已登录的用户只能看到和访问他们有权限访问的页面或功能。用于更细粒度的权限控制。 4. 例如:一个已经登录的管理员,根据其角色和权限,能访问所有的页面,而普通用户则无法看到特定的管理页面。 ErrorRoutes:错误路由配置,包含处理错误页面(如 404 页面)的路由。 1. 统一管理和配置错误页面的路由(403未授权、404未找到、500服务器错误) 2. 确保在发生错误时用户能够看到友好的错误提示页面

前端路由的主要核心功能

  1. 基本路由功能
    1. 根据 URL 跳转到相应的页面组件
    2. 支持路由嵌套
    3. 支持路径参数、查询参数等,使页面内容可以根据 URL 动态变化
  2. 错误处理
    1. 处理未定义的路由 404页面
    2. 处理用户无权限访问页面,权限错误页面(如403错误 - 权限不足)
    3. 处理服务器错误(500)
    4. tip1: 通常来说在前端,路由层面的错误拦截,只建议做这三种404、403、500处理。其他的错误都移交到接口层面使用message之类的组件进行提示。而不是通过跳转路由展示整个页面的方式。403、500 和 404 是严重的错误,通常需要通过专门的错误页面进行处理,以确保用户得到明确的反馈和指导。
    5. tip2:较轻的错误,比如 400 错误(请求无效),这些错误通常发生在某个特定页面的某个接口请求中,与整个应用的全局状态关系不大。这些错误可以通过全局提示组件来处理,而不需要跳转到一个专门的错误页面。这种处理方式能够为用户提供即时的反馈。
  3. 权限控制
    1. 认证检查(useAuth):确保用户在访问受保护路由时已经登录,如果未登录则重定向到登录页面
    2. 角色权限:根据用户的角色或权限动态生成可访问的路由,确保用户只能看到和访问他们有权限访问的页面
    3. 路由守卫:通过路由守卫(AuthGuard)在路由层面实现权限控制,保护特定路由
  4. 性能优化
    1. 使用 React.lazySuspense 懒加载路由组件,减少初始化加载时间
  5. 路由配置管理
  6. 前端路由切换用户体验和SEO
  7. 国际化支持

认证检查和路由守卫都是什么?

认证检查:验证用户是否已经登录,是权限控制的首要步骤,检查用户访问路由时是否已登录,如果未登录则重定向到登录页面。这通常是一个独立的函数或Hook,用户获取用户认证状态。这是一种固定形式,确保用户已经登录 路由守卫:在路由层面进行权限控制,确保用户符合特定条件才能访问相应的路由。除了认证检查,还包括其他检查,例如角色检查、特定条件检查等。通常通过 AuthGuard 在路由配置中拦截并检查导航,决定是否允许用户访问某个路由。这是一个更大范围的概念,包含认证检查及其他条件检查。

React.lazySuspense 是什么?

React.lazy 是 React 提供的用于懒加载组件的函数。它允许你定义一个动态加载的组件,并在需要时(如用户导航到特定路由)才进行加载。

  1. React.lazy 通过动态 import 语法(即 import() 函数)来加载模块。
  2. 它返回一个包含动态加载组件的 Promise,在组件被实际需要时才会触发加载。
  3. 解决的核心问题:减少初始加载时间,使应用在初次加载时只加载必要的部分,其它部分在需要时才加载。
  4. 不适用会怎么样?:如果不使用懒加载,所有组件都会在初始加载时被下载和解析,导致初始加载时间较长,会加载大量用户可能不会立即访问的资源,浪费带宽和内存。

Suspense 是 React 提供的一个组件,用于在懒加载组件还未加载完毕时显示备用内容(如加载动画)。

  1. Suspense 组件包裹需要懒加载的组件,并接收一个 fallback 属性,该属性指定在等待加载时显示的内容。
  2. 当 React.lazy 加载的组件仍在加载时,Suspense 会显示 fallback 内容,直到组件加载完毕。

React.lazy 用于懒加载组件,而 Suspense 用于在懒加载过程中提供备用内容,防止白屏,提升用户体验。

import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { CircleLoading } from './components/CircleLoading';

const HomePage = lazy(() => import('./pages/HomePage'));
const LoginPage = lazy(() => import('./pages/LoginPage'));
const AdminPage = lazy(() => import('./pages/AdminPage'));

function AppRouter() {
  return (
    <Router>
      <Suspense fallback={<CircleLoading />}>
        <Routes>
          <Route path="/" element={<HomePage />} />
          <Route path="/login" element={<LoginPage />} />
          <Route path="/admin" element={<AdminPage />} />
          <Route path="*" element={<div>404 Not Found</div>} />
        </Routes>
      </Suspense>
    </Router>
  );
}

export default AppRouter;

这些功能是如何实现的?当你在使用 Vite(或其他构建工具) 构建 React 应用并且使用了 React.lazy 和 Suspense 来实现懒加载时,Vite 会将每个懒加载的组件单独打包成一个 JavaScript 文件。这些文件只有在用户导航到相应的路由时才会被请求和加载。

  1. React.lazy 使用 JavaScript 的动态 import 语法来懒加载组件。这会告诉 Vite 在构建时将这些组件分离成独立的代码块(chunk)。
  2. 在构建过程中,Vite 会将动态导入的组件打包成独立的 JavaScript 文件(chunk)。这些文件只有在需要时(例如用户导航到对应的路由时)才会被加载。(例如:如果 HomePage 被懒加载,Vite 会生成一个 HomePage.[hash].js 文件。)
  3. 当用户导航到一个懒加载的路由时,React 会触发动态 import,从服务器请求对应的 JavaScript 文件,加载并渲染组件。
dist/
├── assets/
   ├── HomePage.[hash].js
   ├── LoginPage.[hash].js
   ├── AdminPage.[hash].js
   └── ...其他资源
├── index.html
├── ...其他文件

加载的过程如下

  1. 用户访问 /(主页),浏览器加载 index.html 和主 JavaScript 文件。
  2. 主 JavaScript 文件包含 React.lazy 和 Suspense 的配置,但不会立即加载 HomePage、LoginPage 和 AdminPage 组件。
  3. 当用户点击导航链接到 /login。
  4. React 触发动态 import,浏览器请求 LoginPage.[hash].js 文件。
  5. 文件加载完成后,React 渲染 LoginPage 组件。

好坏参半

这种优化方案,针对中大型应用来说是非常有效的性能优化策略。有一些使用创建需要动态考虑,如何使用这些方案

不适用懒加载的场景

  1. 小型应用,页面和功能模块就几个,初次加载时间已经很短
  2. 一些用户频繁访问的页面或功能

使用懒加载的创建

  1. 希望尽量优化初次加载时间,提升首次访问体验的
  2. 大型应用:有多个页面和模块,并且里面的功能较重的情况,初次加载不需要加载所有内容
  3. 一些页面或功能不经常被访问,可以使用懒加载减少初次加载的负担

首屏加载时间(TTFP):一个关键的性能指标,衡量用户首次看到页面内容所需的时间。谷歌建议:首屏加载时间应尽量保持在 1-2秒 内。页面主要内容的加载时间,理想情况下应在 2.5秒 内。页面从加载到用户可以与其交互所需的时间,理想情况下应在 5秒 内。

理想的首屏加载时间

  • 1秒以内:用户感觉几乎是瞬间加载,体验最佳。
  • 1-2秒:仍然是优秀的加载时间,用户感觉响应迅速。
  • 2-3秒:可以接受的加载时间,用户仍然能接受。
  • 超过3秒:用户可能会感到等待时间过长,体验会受到影响。

前端路由中最重要的部分

  1. 基本路由功能
    1. 根据URL跳转到相应的页面组件
    2. 支持嵌套路由
    3. 支持路径参数、查询参数,页面可以根据URL动态变化
  2. 错误处理
  3. 权限控制
  4. 性能优化

下面一章,我们先走一波基础案例,将给予 Vite + React 启一个最基础的案例,了解这些部分。然后基于此去查看 slash-admin 项目