进阶功能

本页面介绍 @feoe/fs-router 的进阶功能和高级用法。

路由守卫

路由守卫允许你在用户访问特定路由前进行权限检查和验证。

使用 Loader 实现路由守卫

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}

状态管理集成

与状态管理库的集成模式。

与 Zustand 集成

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}