故障排除

系统性的问题诊断和解决方法,帮助您快速定位和解决 @feoe/fs-router 相关问题。

诊断流程

1. 问题分类

首先确定问题类型:

  • 配置问题 - 插件配置、构建配置
  • 路由问题 - 路由不生效、参数获取失败
  • 类型问题 - TypeScript 错误、类型不匹配
  • 性能问题 - 加载慢、内存泄漏
  • 构建问题 - 打包失败、部署错误

2. 收集信息

1# 收集环境信息
2node --version
3npm --version
4# 或
5yarn --version
6
7# 查看项目依赖
8npm list @feoe/fs-router
9npm list react-router-dom
10npm list typescript
11
12# 检查构建工具版本
13npm list vite
14# 或
15npm list webpack

3. 启用调试模式

1// vite.config.ts
2export default defineConfig({
3  plugins: [
4    FileBasedRouterVite({
5      routesDirectory: 'src/routes',
6      generatedRoutesPath: 'src/routes.tsx',
7      
8      // 启用调试
9      development: {
10        logGeneration: true,
11        showDebugInfo: true,
12        validateRoutes: true
13      }
14    })
15  ]
16})

常见问题诊断

路由文件未生成

症状: src/routes.tsx 文件不存在或内容为空

诊断步骤:

  1. 检查插件配置

    1// 确认插件已正确添加到配置中
    2console.log('Vite 插件:', config.plugins)
  2. 检查路由目录

    1# 确认目录存在且包含路由文件
    2ls -la src/routes/
    3find src/routes -name "*.tsx" -o -name "*.ts"
  3. 检查文件权限

    1# 确认有写入权限
    2touch src/routes.tsx
    3ls -la src/routes.tsx
  4. 查看构建日志

    1# 启动开发服务器并查看日志
    2npm run dev --verbose

解决方案:

1// 确保配置正确
2FileBasedRouterVite({
3  routesDirectory: 'src/routes',        // 确保路径正确
4  generatedRoutesPath: 'src/routes.tsx', // 确保父目录存在
5  enableGeneration: true,               // 确保启用生成
6  watch: true                          // 确保启用监听
7})

TypeScript 类型错误

症状: 编译时出现类型相关错误

诊断步骤:

  1. 检查 TypeScript 配置

    1// tsconfig.json
    2{
    3  "compilerOptions": {
    4    "moduleResolution": "node",
    5    "esModuleInterop": true,
    6    "allowSyntheticDefaultImports": true,
    7    "jsx": "react-jsx"
    8  },
    9  "include": [
    10    "src/**/*",
    11    "src/routes.tsx"  // 确保包含生成的文件
    12  ]
    13}
  2. 检查类型生成

    1# 确认类型文件是否生成
    2ls -la src/routes-type.ts
  3. 验证导入路径

    1// 检查导入是否正确
    2import { routes } from '@/routes'  // 使用别名
    3// 或
    4import { routes } from './routes'  // 使用相对路径

解决方案:

1// 启用类型生成
2FileBasedRouterVite({
3  enableGeneration: true,
4  typeGenerateOptions: {
5    routesTypeFile: 'src/routes-type.ts',
6    generateRouteParams: true,
7    generateLoaderTypes: true,
8    routesDirectories: []
9  }
10})

路由不匹配

症状: 访问路由时显示 404 或错误页面

诊断步骤:

  1. 检查生成的路由配置

    1// 在控制台查看生成的路由
    2import { routes } from '@/routes'
    3console.log('生成的路由:', JSON.stringify(routes, null, 2))
  2. 验证文件结构

    1# 检查文件命名和结构
    2tree src/routes/
  3. 检查路由器配置

    1// 确认路由器配置正确
    2const router = createBrowserRouter(routes)
    3// 或
    4const router = createHashRouter(routes)

解决方案:

1// 确保文件命名正确
2src/routes/
3├── page.tsx          # 对应 /
4├── about/
5│   └── page.tsx      # 对应 /about
6└── user/
7    └── [id]/
8        └── page.tsx  # 对应 /user/:id
9
10// 确保组件正确导出
11export default function HomePage() {
12  return <div>首页</div>
13}

动态路由参数问题

症状: useParams() 返回 undefined 或错误的值

诊断步骤:

  1. 检查文件命名

    1# 确认使用方括号命名
    2ls -la src/routes/user/[id]/
  2. 检查参数获取

    1// 调试参数获取
    2const params = useParams()
    3console.log('路由参数:', params)
  3. 验证路由匹配

    1// 检查当前路由
    2const location = useLocation()
    3console.log('当前路径:', location.pathname)

解决方案:

1// 正确的文件命名和参数获取
2// 文件: src/routes/user/[id]/page.tsx
3import { useParams } from 'react-router-dom'
4
5export default function UserPage() {
6  const { id } = useParams()  // 参数名对应文件名
7  
8  if (!id) {
9    return <div>用户 ID 不存在</div>
10  }
11  
12  return <div>用户 ID: {id}</div>
13}

高级诊断技巧

使用 React Developer Tools

  1. 安装扩展

    • Chrome: React Developer Tools
    • Firefox: React Developer Tools
  2. 检查路由状态

    1// 在组件中添加调试信息
    2const location = useLocation()
    3const params = useParams()
    4const matches = useMatches()
    5
    6console.log('路由调试信息:', {
    7  location,
    8  params,
    9  matches
    10})

网络请求调试

1// 监控 loader 请求
2export async function loader({ params, request }) {
3  console.log('Loader 调用:', {
4    params,
5    url: request.url,
6    method: request.method
7  })
8  
9  try {
10    const data = await fetchData(params.id)
11    console.log('Loader 数据:', data)
12    return data
13  } catch (error) {
14    console.error('Loader 错误:', error)
15    throw error
16  }
17}

性能分析

1// 测量组件渲染时间
2import { Profiler } from 'react'
3
4function onRenderCallback(id, phase, actualDuration, baseDuration, startTime, commitTime) {
5  console.log('组件性能:', {
6    id,
7    phase,
8    actualDuration,
9    baseDuration
10  })
11  
12  if (actualDuration > 16) {
13    console.warn(`组件 ${id} 渲染时间过长: ${actualDuration}ms`)
14  }
15}
16
17export default function App() {
18  return (
19    <Profiler id="App" onRender={onRenderCallback}>
20      <RouterProvider router={router} />
21    </Profiler>
22  )
23}

错误处理和日志

全局错误捕获

1// 全局错误边界
2class GlobalErrorBoundary extends Component {
3  constructor(props) {
4    super(props)
5    this.state = { hasError: false, error: null }
6  }
7  
8  static getDerivedStateFromError(error) {
9    return { hasError: true, error }
10  }
11  
12  componentDidCatch(error, errorInfo) {
13    console.error('全局错误:', error, errorInfo)
14    
15    // 发送错误报告
16    this.reportError(error, errorInfo)
17  }
18  
19  reportError(error, errorInfo) {
20    // 发送到错误监控服务
21    if (typeof window !== 'undefined' && window.gtag) {
22      window.gtag('event', 'exception', {
23        description: error.toString(),
24        fatal: false
25      })
26    }
27  }
28  
29  render() {
30    if (this.state.hasError) {
31      return (
32        <div className="error-page">
33          <h1>应用出现错误</h1>
34          <details>
35            <summary>错误详情</summary>
36            <pre>{this.state.error?.toString()}</pre>
37          </details>
38        </div>
39      )
40    }
41    
42    return this.props.children
43  }
44}

结构化日志

1// 日志工具
2class Logger {
3  static debug(message: string, data?: any) {
4    if (process.env.NODE_ENV === 'development') {
5      console.log(`[DEBUG] ${message}`, data)
6    }
7  }
8  
9  static info(message: string, data?: any) {
10    console.info(`[INFO] ${message}`, data)
11  }
12  
13  static warn(message: string, data?: any) {
14    console.warn(`[WARN] ${message}`, data)
15  }
16  
17  static error(message: string, error?: Error, data?: any) {
18    console.error(`[ERROR] ${message}`, error, data)
19    
20    // 发送到错误监控服务
21    this.reportError(message, error, data)
22  }
23  
24  private static reportError(message: string, error?: Error, data?: any) {
25    // 实现错误报告逻辑
26  }
27}
28
29// 在路由中使用
30export async function loader({ params }) {
31  Logger.debug('加载用户数据', { userId: params.id })
32  
33  try {
34    const user = await fetchUser(params.id)
35    Logger.info('用户数据加载成功', { userId: params.id })
36    return { user }
37  } catch (error) {
38    Logger.error('用户数据加载失败', error, { userId: params.id })
39    throw error
40  }
41}

问题报告模板

当需要寻求帮助时,请提供以下信息:

1## 问题描述
2[简要描述遇到的问题]
3
4## 环境信息
5- Node.js 版本: 
6- 包管理器: npm/yarn/pnpm
7- @feoe/fs-router 版本: 
8- React 版本: 
9- React Router 版本: 
10- 构建工具: Vite/Webpack/Rspack
11- 操作系统: 
12
13## 重现步骤
141. 
152. 
163. 
17
18## 期望行为
19[描述期望的正确行为]
20
21## 实际行为
22[描述实际发生的行为]
23
24## 配置文件
25```typescript
26// vite.config.ts 或 webpack.config.js

错误信息

[粘贴完整的错误信息和堆栈跟踪]

最小复现示例

[提供最小的可复现示例代码]

## 预防措施 ### 代码审查清单 - [ ] 文件命名符合约定 - [ ] 组件正确导出 - [ ] 路由配置正确 - [ ] TypeScript 类型正确 - [ ] 错误处理完善 - [ ] 性能考虑充分 ### 自动化测试 ```tsx // 路由测试示例 import { render, screen } from '@testing-library/react' import { createMemoryRouter, RouterProvider } from 'react-router-dom' import { routes } from '@/routes' describe('路由测试', () => { test('首页路由正常工作', () => { const router = createMemoryRouter(routes, { initialEntries: ['/'] }) render(<RouterProvider router={router} />) expect(screen.getByText('首页')).toBeInTheDocument() }) test('动态路由参数正确传递', () => { const router = createMemoryRouter(routes, { initialEntries: ['/user/123'] }) render(<RouterProvider router={router} />) expect(screen.getByText('用户 ID: 123')).toBeInTheDocument() }) })