Back to Prompt Optimizer

Electron API 重构与回滚经验记录

docs/archives/007-electron-api-refactor-rollback.md

2.10.25.4 KB
Original Source

Electron API 重构与回滚经验记录

📅 时间线

  • 2025-07-14: 发现版本检查功能报错 "Failed to check versions"
  • 重构提交: 12f6f49 - "feat(ui): 添加 Electron API Hook并重构更新管理"
  • 问题根源: 过度抽象导致的架构复杂性和 bug

🚨 问题描述

症状

useUpdater.ts:224 [useUpdater] Error checking all versions: Error: Failed to check versions
    at g (useUpdater.ts:128:15)

主进程日志正常

[DESKTOP] [2025-07-14 00:20:57] [info] Unified version check completed: { stable: '1.2.5', prerelease: '1.2.5' }

前端收到的响应

javascript
{
  currentVersion: '1.2.0',
  stable: { hasUpdate: true, remoteVersion: '1.2.5', ... },
  prerelease: { hasUpdate: true, remoteVersion: '1.2.5', ... }
}
// 但是 response.success 是 undefined

🔍 根本原因分析

重构前(工作正常)

typescript
// 简单直接
const results = await window.electronAPI!.updater.checkAllVersions()

重构后(引入问题)

typescript
// 过度抽象
const { updater } = useElectronAPI()
const response = await updater.checkAllVersions()
if (!response.success) {  // response.success 是 undefined
  throw new Error(response.error || 'Failed to check versions')
}
const results = response.data

问题链条

  1. useUpdater.ts 调用 getElectronAPI() 而不是 useElectronAPI()
  2. getElectronAPI() 直接返回 window.electronAPI,绕过了包装器
  3. preload.js 返回 result.data(直接数据)
  4. useElectronAPI.ts 期望 {success, data, error} 格式
  5. 数据格式不匹配 导致 response.successundefined

🎯 重构的初衷 vs 实际效果

初衷

  • 避免类型错误和 IDE 警告
  • 提供类型安全的 Electron API 访问

实际效果

  • 引入了过度复杂的抽象层
  • 增加了调试难度
  • 创造了新的 bug
  • 维护成本大幅增加

🔄 回滚操作记录

1. 删除过度抽象文件

bash
rm packages/ui/src/composables/useElectronAPI.ts

2. 回滚 useUpdater.ts

  • 移除 useElectronAPI 导入
  • 将所有 electronUpdater 改为 window.electronAPI.updater
  • 将所有 electronShell 改为 window.electronAPI.shell
  • 将所有 electronOn/electronOff 改为 window.electronAPI.on/off
  • 移除复杂的响应格式检查

3. 简化类型定义

typescript
// packages/ui/src/types/electron.d.ts
interface UpdaterAPI {
  checkAllVersions(): Promise<{
    currentVersion: string
    stable?: { remoteVersion?: string, hasUpdate?: boolean, ... }
    prerelease?: { remoteVersion?: string, hasUpdate?: boolean, ... }
  }>
  installUpdate(): Promise<void>
  ignoreVersion(version: string, versionType?: 'stable' | 'prerelease'): Promise<void>
}

interface ShellAPI {
  openExternal(url: string): Promise<void>
}

4. 保持 preload.js 简单

javascript
checkAllVersions: async () => {
  const result = await withTimeout(
    ipcRenderer.invoke(IPC_EVENTS.UPDATE_CHECK_ALL_VERSIONS),
    60000
  );
  if (!result.success) {
    throw new Error(result.error);
  }
  return result.data;  // 直接返回数据
}

📚 经验教训

❌ 过度工程化的问题

  1. 复杂度爆炸: 为了解决简单问题引入复杂架构
  2. 调试困难: 多层抽象使问题定位变得复杂
  3. 维护成本: 需要维护额外的 Hook、类型定义、包装逻辑
  4. 新 bug 源: 抽象层本身成为 bug 的来源

✅ 正确的解决方案

  1. 简单的类型定义: 通过完善 electron.d.ts 解决 IDE 警告
  2. 直接 API 调用: 保持代码简洁明了
  3. 最小化抽象: 只在真正需要时才引入抽象

🎯 设计原则

  1. KISS 原则: Keep It Simple, Stupid
  2. YAGNI 原则: You Aren't Gonna Need It
  3. 优先解决核心问题: 类型安全 ≠ 复杂抽象
  4. 渐进式改进: 从简单开始,必要时再抽象

🔧 最佳实践

解决 IDE 警告的正确方法

typescript
// ✅ 正确:完善类型定义
declare global {
  interface Window {
    electronAPI: {
      updater: UpdaterAPI
      shell: ShellAPI
      on: (event: string, callback: Function) => void
      off: (event: string, callback: Function) => void
    }
  }
}

// ✅ 正确:直接使用
const result = await window.electronAPI.updater.checkAllVersions()

避免过度抽象

typescript
// ❌ 错误:不必要的包装
const { updater } = useElectronAPI()
const response = await updater.checkAllVersions()
const result = response.data

// ✅ 正确:直接调用
const result = await window.electronAPI.updater.checkAllVersions()

🎉 结果

回滚后的优势

  • 代码行数减少: 删除了 100+ 行的包装代码
  • 调试简化: 问题直接定位到源头
  • 类型安全: 通过类型定义实现,无运行时开销
  • 维护简单: 减少了抽象层的维护负担

性能提升

  • 减少函数调用: 直接 API 调用,无包装开销
  • 减少内存占用: 无额外的包装对象
  • 提高可读性: 代码意图更加明确

💡 未来指导原则

  1. 先解决问题,再考虑抽象
  2. 类型安全通过类型定义实现,而非运行时包装
  3. 保持 API 调用的直接性和透明性
  4. 抽象必须有明确的价值,而非为了抽象而抽象
  5. 重构前要充分评估复杂度收益比

教训: 有时候最好的重构就是不重构。简单的问题用简单的方法解决。