You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

8.9 KiB

路由设计

文档版本:v1.1
最后更新:2025-01-18


目录

  1. 路由结构
  2. 路由配置
  3. 路由守卫
  4. 路由辅助函数

1. 路由结构

1.1 路由常量定义

// src/constants/routes.ts
export const ROUTES = {
  // 公开页面
  HOME: "/",
  LOGIN: "/login",
  REGISTER: "/register",

  // 项目管理
  PROJECTS: "/projects",
  PROJECT_DETAIL: "/projects/:projectId",

  // 编辑器
  EDITOR: "/editor/:projectId",

  // 设置
  SETTINGS: "/settings",
  PROFILE: "/settings/profile",
  PREFERENCES: "/settings/preferences",

  // 其他
  NOT_FOUND: "/404",
} as const;

1.2 路由层级

/                           # 首页
├── /login                  # 登录页
├── /register               # 注册页
├── /projects               # 项目列表
│   └── /:projectId         # 项目详情/编辑器
├── /settings               # 设置
│   ├── /profile            # 个人资料
│   └── /preferences        # 偏好设置
└── /404                    # 404 页面

2. 路由配置

2.1 Router 配置

// src/app/Router.tsx
import { createBrowserRouter, RouterProvider, Outlet } from 'react-router-dom';
import { lazy, Suspense } from 'react';
import { ROUTES } from '@constants/routes';
import { LoadingSpinner } from '@components/common';
import { ProtectedRoute } from './ProtectedRoute';
import { AppLayout } from '@components/layout/AppLayout';

// 懒加载页面
const HomePage = lazy(() => import('@/pages/HomePage'));
const LoginPage = lazy(() => import('@/pages/LoginPage'));
const ProjectsPage = lazy(() => import('@/pages/ProjectsPage'));
const ProjectPage = lazy(() => import('@/pages/ProjectPage'));
const NotFoundPage = lazy(() => import('@/pages/NotFoundPage'));

// 页面加载 Wrapper
function PageLoader({ children }: { children: React.ReactNode }) {
  return (
    <Suspense fallback={<LoadingSpinner fullScreen />}>
      {children}
    </Suspense>
  );
}

const router = createBrowserRouter([
  {
    path: ROUTES.HOME,
    element: <PageLoader><HomePage /></PageLoader>,
  },
  {
    path: ROUTES.LOGIN,
    element: <PageLoader><LoginPage /></PageLoader>,
  },
  {
    path: ROUTES.PROJECTS,
    element: (
      <ProtectedRoute>
        <PageLoader><ProjectsPage /></PageLoader>
      </ProtectedRoute>
    ),
  },
  {
    path: ROUTES.PROJECT_DETAIL,
    element: (
      <ProtectedRoute>
        <PageLoader><ProjectPage /></PageLoader>
      </ProtectedRoute>
    ),
  },
  {
    path: '*',
    element: <PageLoader><NotFoundPage /></PageLoader>,
  },
]);

export function Router() {
  return <RouterProvider router={router} />;
}

2.2 嵌套路由示例

// 如果需要嵌套路由
const router = createBrowserRouter([
  {
    path: '/',
    element: <AppLayout />,
    children: [
      {
        index: true,
        element: <HomePage />,
      },
      {
        path: 'projects',
        element: <Outlet />,
        children: [
          {
            index: true,
            element: <ProjectsPage />,
          },
          {
            path: ':projectId',
            element: <ProjectPage />,
          },
        ],
      },
    ],
  },
]);

3. 路由守卫

3.1 认证守卫

// src/app/ProtectedRoute.tsx
import { Navigate, Outlet, useLocation } from 'react-router-dom';
import { useAppStore } from '@stores/appStore';
import { ROUTES } from '@constants/routes';

export function ProtectedRoute() {
  const user = useAppStore((state) => state.user);
  const location = useLocation();

  if (!user) {
    // 保存当前位置,登录后跳转回来
    return <Navigate to={ROUTES.LOGIN} state={{ from: location }} replace />;
  }

  return <Outlet />;
}

3.2 权限守卫

// src/app/RoleGuard.tsx
import { Navigate } from 'react-router-dom';
import { useAppStore } from '@stores/appStore';
import { ROUTES } from '@constants/routes';

interface RoleGuardProps {
  children: React.ReactNode;
  allowedRoles: string[];
}

export function RoleGuard({ children, allowedRoles }: RoleGuardProps) {
  const user = useAppStore((state) => state.user);

  if (!user || !allowedRoles.includes(user.role)) {
    return <Navigate to={ROUTES.HOME} replace />;
  }

  return <>{children}</>;
}

// 使用示例
<RoleGuard allowedRoles={['admin', 'editor']}>
  <AdminPanel />
</RoleGuard>

3.3 登录后重定向

// src/pages/LoginPage.tsx
import { useNavigate, useLocation } from 'react-router-dom';
import { useAppStore } from '@stores/appStore';
import { ROUTES } from '@constants/routes';

function LoginPage() {
  const navigate = useNavigate();
  const location = useLocation();
  const setUser = useAppStore((state) => state.setUser);

  const handleLogin = async (credentials: LoginCredentials) => {
    const user = await loginApi(credentials);
    setUser(user);

    // 登录后跳转到之前的页面,或默认跳转到项目列表
    const from = location.state?.from?.pathname || ROUTES.PROJECTS;
    navigate(from, { replace: true });
  };

  return <LoginForm onSubmit={handleLogin} />;
}

4. 路由辅助函数

4.1 路径生成函数

// src/constants/routes.ts

// 路由辅助函数
export const getProjectPath = (projectId: string) => `/projects/${projectId}`;
export const getEditorPath = (projectId: string) => `/editor/${projectId}`;
export const getSettingsPath = (tab?: string) =>
  tab ? `/settings/${tab}` : '/settings';

// 使用示例
import { useNavigate } from 'react-router-dom';
import { getProjectPath } from '@constants/routes';

function ProjectCard({ project }: { project: Project }) {
  const navigate = useNavigate();

  const handleClick = () => {
    navigate(getProjectPath(project.id));
  };

  return <div onClick={handleClick}>{project.name}</div>;
}

4.2 路由参数 Hook

// src/hooks/useProjectRoute.ts
import { useParams, useNavigate } from 'react-router-dom';
import { ROUTES } from '@constants/routes';

export function useProjectRoute() {
  const { projectId } = useParams<{ projectId: string }>();
  const navigate = useNavigate();

  const goToProject = (id: string) => {
    navigate(`/projects/${id}`);
  };

  const goToProjects = () => {
    navigate(ROUTES.PROJECTS);
  };

  return {
    projectId,
    goToProject,
    goToProjects,
  };
}

// 使用示例
function ProjectPage() {
  const { projectId, goToProjects } = useProjectRoute();

  if (!projectId) {
    return <Navigate to={ROUTES.PROJECTS} />;
  }

  return (
    <div>
      <button onClick={goToProjects}>返回项目列表</button>
      <ProjectEditor projectId={projectId} />
    </div>
  );
}

4.3 查询参数处理

// src/hooks/useQueryParams.ts
import { useSearchParams } from 'react-router-dom';

export function useQueryParams() {
  const [searchParams, setSearchParams] = useSearchParams();

  const getParam = (key: string) => searchParams.get(key);

  const setParam = (key: string, value: string) => {
    const newParams = new URLSearchParams(searchParams);
    newParams.set(key, value);
    setSearchParams(newParams);
  };

  const removeParam = (key: string) => {
    const newParams = new URLSearchParams(searchParams);
    newParams.delete(key);
    setSearchParams(newParams);
  };

  return {
    getParam,
    setParam,
    removeParam,
    searchParams,
  };
}

// 使用示例
function ProjectsPage() {
  const { getParam, setParam } = useQueryParams();
  const filter = getParam('filter') || 'all';

  const handleFilterChange = (newFilter: string) => {
    setParam('filter', newFilter);
  };

  return (
    <div>
      <FilterTabs value={filter} onChange={handleFilterChange} />
      <ProjectList filter={filter} />
    </div>
  );
}

5. 路由最佳实践

5.1 懒加载

// ✅ 推荐:使用懒加载
const ProjectPage = lazy(() => import("@/pages/ProjectPage"));

// ❌ 避免:直接导入大型页面
import ProjectPage from "@/pages/ProjectPage";

5.2 错误边界

// src/app/Router.tsx
import { ErrorBoundary } from '@components/common/ErrorBoundary';

const router = createBrowserRouter([
  {
    path: '/',
    element: <AppLayout />,
    errorElement: <ErrorBoundary />,
    children: [
      // ... 子路由
    ],
  },
]);

5.3 路由预加载

// 鼠标悬停时预加载
import { Link } from 'react-router-dom';

function ProjectCard({ project }: { project: Project }) {
  const handleMouseEnter = () => {
    // 预加载项目页面
    import('@/pages/ProjectPage');
  };

  return (
    <Link
      to={`/projects/${project.id}`}
      onMouseEnter={handleMouseEnter}
    >
      {project.name}
    </Link>
  );
}

相关文档


最后更新:2025-01-18 | 版本:v1.1