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
8.9 KiB
路由设计
文档版本:v1.1
最后更新:2025-01-18
目录
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