本页面介绍 @feoe/fs-router 的进阶功能和高级用法。
路由守卫允许你在用户访问特定路由前进行权限检查和验证。
1// src/routes/admin/page.tsx
2import { redirect } from 'react-router-dom'
3
4export async function loader({ request }) {
5 const user = await getCurrentUser(request)
6
7 // 检查用户是否已登录
8 if (!user) {
9 throw redirect('/login')
10 }
11
12 // 检查用户权限
13 if (!user.roles.includes('admin')) {
14 throw new Response('权限不足', { status: 403 })
15 }
16
17 return { user }
18}
19
20export default function AdminPage() {
21 const { user } = useLoaderData()
22
23 return (
24 <div>
25 <h1>管理后台</h1>
26 <p>欢迎,{user.name}</p>
27 </div>
28 )
29}
1// src/utils/auth.ts
2export async function requireAuth(request: Request) {
3 const user = await getCurrentUser(request)
4
5 if (!user) {
6 throw redirect('/login')
7 }
8
9 return user
10}
11
12export async function requireRole(request: Request, role: string) {
13 const user = await requireAuth(request)
14
15 if (!user.roles.includes(role)) {
16 throw new Response('权限不足', { status: 403 })
17 }
18
19 return user
20}
21
22// 在路由中使用
23export async function loader({ request }) {
24 const user = await requireRole(request, 'admin')
25 return { user }
26}
通过 Action 和 Loader 实现类似中间件的功能。
1// src/utils/middleware.ts
2export async function withAuth(handler: Function) {
3 return async (args: any) => {
4 const user = await getCurrentUser(args.request)
5
6 if (!user) {
7 throw redirect('/login')
8 }
9
10 return handler({ ...args, user })
11 }
12}
13
14// 使用中间件
15export const loader = withAuth(async ({ params, user }) => {
16 const data = await fetchUserData(params.id, user)
17 return { data }
18})
1export function withLogging(handler: Function, name: string) {
2 return async (args: any) => {
3 console.log(`[${name}] 开始执行`, args.params)
4 const start = performance.now()
5
6 try {
7 const result = await handler(args)
8 const end = performance.now()
9 console.log(`[${name}] 执行成功,耗时: ${end - start}ms`)
10 return result
11 } catch (error) {
12 console.error(`[${name}] 执行失败:`, error)
13 throw error
14 }
15 }
16}
深入了解嵌套路由的高级用法。
src/routes/
├── layout.tsx # 根布局
├── (app)/ # 应用路由组
│ ├── layout.tsx # 应用布局
│ ├── dashboard/
│ │ ├── layout.tsx # 仪表板布局
│ │ ├── page.tsx # /dashboard
│ │ ├── analytics/
│ │ │ └── page.tsx # /dashboard/analytics
│ │ └── settings/
│ │ ├── layout.tsx # 设置布局
│ │ ├── page.tsx # /dashboard/settings
│ │ ├── profile/
│ │ │ └── page.tsx # /dashboard/settings/profile
│ │ └── security/
│ │ └── page.tsx # /dashboard/settings/security
│ └── user/
│ ├── layout.tsx # 用户布局
│ ├── page.tsx # /user
│ └── [id]/
│ ├── layout.tsx # 用户详情布局
│ ├── page.tsx # /user/:id
│ ├── posts/
│ │ └── page.tsx # /user/:id/posts
│ └── settings/
│ └── page.tsx # /user/:id/settings
1// src/routes/(app)/layout.tsx
2export async function loader({ request }) {
3 const user = await getCurrentUser(request)
4 const notifications = await getNotifications(user.id)
5
6 return { user, notifications }
7}
8
9export default function AppLayout() {
10 const { user, notifications } = useLoaderData()
11
12 return (
13 <div className="app-layout">
14 <Header user={user} notifications={notifications} />
15 <main>
16 <Outlet />
17 </main>
18 </div>
19 )
20}
实现高效的代码分割和懒加载。
1// src/routes/heavy-page/page.tsx
2import { lazy, Suspense } from 'react'
3
4const HeavyChart = lazy(() => import('@/components/HeavyChart'))
5const DataTable = lazy(() => import('@/components/DataTable'))
6
7export default function HeavyPage() {
8 return (
9 <div>
10 <h1>数据分析</h1>
11
12 <Suspense fallback={<div>加载图表中...</div>}>
13 <HeavyChart />
14 </Suspense>
15
16 <Suspense fallback={<div>加载表格中...</div>}>
17 <DataTable />
18 </Suspense>
19 </div>
20 )
21}
1// src/routes/dashboard/page.tsx
2import { lazy } from 'react'
3
4// 动态导入子组件
5const loadAnalytics = () => import('./analytics/page')
6const loadReports = () => import('./reports/page')
7
8export default function DashboardPage() {
9 const [activeTab, setActiveTab] = useState('overview')
10
11 return (
12 <div>
13 <nav>
14 <button onClick={() => setActiveTab('overview')}>概览</button>
15 <button onClick={() => setActiveTab('analytics')}>分析</button>
16 <button onClick={() => setActiveTab('reports')}>报告</button>
17 </nav>
18
19 {activeTab === 'analytics' && (
20 <Suspense fallback={<div>加载分析页面...</div>}>
21 <AsyncComponent loader={loadAnalytics} />
22 </Suspense>
23 )}
24
25 {activeTab === 'reports' && (
26 <Suspense fallback={<div>加载报告页面...</div>}>
27 <AsyncComponent loader={loadReports} />
28 </Suspense>
29 )}
30 </div>
31 )
32}
优化用户体验的数据预取策略。
1import { useFetcher } from 'react-router-dom'
2
3export default function UserCard({ userId }) {
4 const fetcher = useFetcher()
5
6 const handleMouseEnter = () => {
7 if (fetcher.state === 'idle' && !fetcher.data) {
8 fetcher.load(`/api/user/${userId}`)
9 }
10 }
11
12 return (
13 <Link
14 to={`/user/${userId}`}
15 onMouseEnter={handleMouseEnter}
16 >
17 <div>用户卡片</div>
18 </Link>
19 )
20}
1import { useEffect, useRef } from 'react'
2import { useFetcher } from 'react-router-dom'
3
4export default function LazyLoadCard({ userId }) {
5 const ref = useRef()
6 const fetcher = useFetcher()
7
8 useEffect(() => {
9 const observer = new IntersectionObserver(
10 ([entry]) => {
11 if (entry.isIntersecting && fetcher.state === 'idle') {
12 fetcher.load(`/api/user/${userId}`)
13 }
14 },
15 { threshold: 0.1 }
16 )
17
18 if (ref.current) {
19 observer.observe(ref.current)
20 }
21
22 return () => observer.disconnect()
23 }, [userId, fetcher])
24
25 return (
26 <div ref={ref}>
27 {fetcher.data ? (
28 <UserDetails user={fetcher.data} />
29 ) : (
30 <div>加载中...</div>
31 )}
32 </div>
33 )
34}
与状态管理库的集成模式。
1// src/stores/userStore.ts
2import { create } from 'zustand'
3
4interface UserStore {
5 user: User | null
6 setUser: (user: User) => void
7 clearUser: () => void
8}
9
10export const useUserStore = create<UserStore>((set) => ({
11 user: null,
12 setUser: (user) => set({ user }),
13 clearUser: () => set({ user: null })
14}))
15
16// 在路由中使用
17export async function loader({ request }) {
18 const user = await getCurrentUser(request)
19
20 // 更新全局状态
21 useUserStore.getState().setUser(user)
22
23 return { user }
24}
高级错误处理和恢复策略。
1export default function ErrorBoundary() {
2 const error = useRouteError()
3 const navigate = useNavigate()
4 const [retryCount, setRetryCount] = useState(0)
5
6 const handleRetry = () => {
7 if (retryCount < 3) {
8 setRetryCount(prev => prev + 1)
9 navigate(0) // 重新加载当前路由
10 }
11 }
12
13 return (
14 <div className="error-page">
15 <h1>出现错误</h1>
16 <p>{error.message}</p>
17
18 {retryCount < 3 && (
19 <button onClick={handleRetry}>
20 重试 ({retryCount}/3)
21 </button>
22 )}
23 </div>
24 )
25}