路由进阶 #
一、路由懒加载 #
1.1 基本懒加载 #
javascript
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
1.2 带加载状态的懒加载 #
javascript
function PageLoader() {
return (
<div className="page-loader">
<div className="spinner" />
<p>加载中...</p>
</div>
);
}
function App() {
return (
<BrowserRouter>
<Suspense fallback={<PageLoader />}>
<Routes>
{/* ... */}
</Routes>
</Suspense>
</BrowserRouter>
);
}
1.3 预加载 #
javascript
const Dashboard = lazy(() => import('./pages/Dashboard'));
function Navbar() {
const preloadDashboard = () => {
import('./pages/Dashboard');
};
return (
<nav>
<Link
to="/dashboard"
onMouseEnter={preloadDashboard}
>
Dashboard
</Link>
</nav>
);
}
二、数据加载 #
2.1 使用loader #
javascript
import { createBrowserRouter, RouterProvider, useLoaderData } from 'react-router-dom';
async function userLoader({ params }) {
const response = await fetch(`/api/users/${params.id}`);
if (!response.ok) {
throw new Response('Not Found', { status: 404 });
}
return response.json();
}
const router = createBrowserRouter([
{
path: '/users/:id',
element: <UserDetail />,
loader: userLoader
}
]);
function UserDetail() {
const user = useLoaderData();
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
2.2 嵌套数据加载 #
javascript
const router = createBrowserRouter([
{
path: '/',
element: <Layout />,
loader: async () => {
return { user: await fetchCurrentUser() };
},
children: [
{
path: 'users',
element: <UserList />,
loader: async () => {
return fetch('/api/users').then(res => res.json());
}
},
{
path: 'users/:id',
element: <UserDetail />,
loader: async ({ params }) => {
return fetch(`/api/users/${params.id}`).then(res => res.json());
}
}
]
}
]);
2.3 使用useRouteLoaderData #
javascript
function UserDetail() {
const user = useRouteLoaderData('user-detail');
return (
<div>
<h1>{user.name}</h1>
</div>
);
}
const router = createBrowserRouter([
{
path: 'users/:id',
element: <UserDetail />,
loader: userLoader,
id: 'user-detail'
}
]);
三、错误处理 #
3.1 errorElement #
javascript
const router = createBrowserRouter([
{
path: '/',
element: <Layout />,
errorElement: <ErrorBoundary />,
children: [
{
path: 'users/:id',
element: <UserDetail />,
loader: userLoader,
errorElement: <UserError />
}
]
}
]);
3.2 useRouteError #
javascript
import { useRouteError, isRouteErrorResponse } from 'react-router-dom';
function ErrorBoundary() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return (
<div>
<h1>{error.status} {error.statusText}</h1>
<p>{error.data}</p>
</div>
);
}
return (
<div>
<h1>出错了</h1>
<p>{error.message}</p>
</div>
);
}
四、路由守卫进阶 #
4.1 高阶路由守卫 #
javascript
function withAuth(WrappedComponent) {
return function WithAuth() {
const { user, loading } = useAuth();
const location = useLocation();
if (loading) {
return <Loading />;
}
if (!user) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
return <WrappedComponent />;
};
}
const ProtectedDashboard = withAuth(Dashboard);
4.2 权限路由组件 #
javascript
function AuthorizedRoute({ children, permissions = [] }) {
const { user, hasPermission } = useAuth();
const location = useLocation();
if (!user) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
const hasAccess = permissions.every(p => hasPermission(p));
if (!hasAccess) {
return <Navigate to="/forbidden" replace />;
}
return children;
}
function App() {
return (
<Routes>
<Route
path="/admin"
element={
<AuthorizedRoute permissions={['admin']}>
<AdminPanel />
</AuthorizedRoute>
}
/>
</Routes>
);
}
4.3 动态路由权限 #
javascript
function useAuthorizedRoutes() {
const { user } = useAuth();
return useMemo(() => {
const routes = [...publicRoutes];
if (user) {
routes.push(...authenticatedRoutes);
if (user.role === 'admin') {
routes.push(...adminRoutes);
}
}
return routes;
}, [user]);
}
五、路由过渡动画 #
5.1 使用React Transition Group #
javascript
import { CSSTransition, TransitionGroup } from 'react-transition-group';
import { useLocation } from 'react-router-dom';
function AnimatedRoutes() {
const location = useLocation();
return (
<TransitionGroup>
<CSSTransition
key={location.pathname}
classNames="fade"
timeout={300}
>
<Routes location={location}>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</CSSTransition>
</TransitionGroup>
);
}
css
.fade-enter {
opacity: 0;
}
.fade-enter-active {
opacity: 1;
transition: opacity 300ms;
}
.fade-exit {
opacity: 1;
}
.fade-exit-active {
opacity: 0;
transition: opacity 300ms;
}
5.2 使用Framer Motion #
javascript
import { motion, AnimatePresence } from 'framer-motion';
import { useLocation } from 'react-router-dom';
function AnimatedRoutes() {
const location = useLocation();
return (
<AnimatePresence mode="wait">
<motion.div
key={location.pathname}
initial={{ opacity: 0, x: 100 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -100 }}
transition={{ duration: 0.3 }}
>
<Routes location={location}>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</motion.div>
</AnimatePresence>
);
}
六、路由元信息 #
6.1 页面标题 #
javascript
function useDocumentTitle(title) {
useEffect(() => {
document.title = title;
}, [title]);
}
function Home() {
useDocumentTitle('首页 - My App');
return <div>Home</div>;
}
// 或使用路由配置
const routes = [
{
path: '/',
element: <Home />,
handle: { title: '首页' }
},
{
path: '/about',
element: <About />,
handle: { title: '关于我们' }
}
];
function Layout() {
const route = useMatches();
const title = route[route.length - 1]?.handle?.title;
useEffect(() => {
if (title) {
document.title = `${title} - My App`;
}
}, [title]);
return <Outlet />;
}
6.2 面包屑导航 #
javascript
const routes = [
{
path: '/',
element: <Layout />,
handle: { crumb: () => <Link to="/">首页</Link> },
children: [
{
path: 'users',
element: <UserList />,
handle: { crumb: () => <Link to="/users">用户</Link> },
children: [
{
path: ':id',
element: <UserDetail />,
handle: {
crumb: ({ params }) => <span>用户 {params.id}</span>
}
}
]
}
]
}
];
function Breadcrumbs() {
const matches = useMatches();
const crumbs = matches
.filter(match => match.handle?.crumb)
.map(match => match.handle.crumb(match.data, match.params));
return (
<nav>
{crumbs.map((crumb, index) => (
<span key={index}>
{crumb}
{index < crumbs.length - 1 && ' > '}
</span>
))}
</nav>
);
}
七、路由钩子 #
7.1 useNavigation #
javascript
function SubmitButton() {
const navigation = useNavigation();
const isSubmitting = navigation.state === 'submitting';
return (
<button disabled={isSubmitting}>
{isSubmitting ? '提交中...' : '提交'}
</button>
);
}
7.2 useBeforeUnload #
javascript
function useBeforeUnload(message) {
useEffect(() => {
const handleBeforeUnload = (event) => {
event.preventDefault();
event.returnValue = message;
return message;
};
window.addEventListener('beforeunload', handleBeforeUnload);
return () => {
window.removeEventListener('beforeunload', handleBeforeUnload);
};
}, [message]);
}
function EditForm() {
const [isDirty, setIsDirty] = useState(false);
useBeforeUnload(isDirty ? '您有未保存的更改,确定要离开吗?' : null);
return <form>...</form>;
}
7.3 useBlocker #
javascript
import { useBlocker } from 'react-router-dom';
function usePrompt(message, when) {
const blocker = useBlocker(when);
useEffect(() => {
if (blocker.state === 'blocked') {
const confirmed = window.confirm(message);
if (confirmed) {
blocker.proceed();
} else {
blocker.reset();
}
}
}, [blocker, message]);
}
function EditForm() {
const [isDirty, setIsDirty] = useState(false);
usePrompt('您有未保存的更改,确定要离开吗?', isDirty);
return <form>...</form>;
}
八、最佳实践 #
8.1 路由配置分离 #
javascript
// routes/index.jsx
export const routes = createRoutes();
// routes/publicRoutes.jsx
export const publicRoutes = [
{ path: '/', element: <Home /> },
{ path: '/login', element: <Login /> }
];
// routes/protectedRoutes.jsx
export const protectedRoutes = [
{
path: '/dashboard',
element: (
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
)
}
];
8.2 类型安全(TypeScript) #
typescript
import { RouteObject } from 'react-router-dom';
interface CustomRouteObject extends RouteObject {
auth?: boolean;
title?: string;
}
const routes: CustomRouteObject[] = [
{
path: '/',
element: <Home />,
title: '首页'
},
{
path: '/admin',
element: <Admin />,
auth: true,
title: '管理后台'
}
];
九、总结 #
| 要点 | 说明 |
|---|---|
| 懒加载 | lazy + Suspense |
| 数据加载 | loader + useLoaderData |
| 错误处理 | errorElement + useRouteError |
| 路由守卫 | 权限控制组件 |
| 过渡动画 | TransitionGroup/Framer Motion |
核心原则:
- 使用懒加载优化首屏性能
- 使用 loader 预加载数据
- 实现完善的错误处理
- 合理组织路由配置
最后更新:2026-03-26