|
| 1 | +# TypeScript 类型问题修复 - clean.ts |
| 2 | + |
| 3 | +## 问题描述 |
| 4 | + |
| 5 | +在 `packages/monorepo/src/commands/clean.ts` 文件中,由于启用了 `exactOptionalPropertyTypes: true` 这个严格的 TypeScript 编译选项,导致以下类型错误: |
| 6 | + |
| 7 | +``` |
| 8 | +不能将类型"string | undefined"分配给类型"string"。 |
| 9 | + 不能将类型"undefined"分配给类型"string"。 |
| 10 | +``` |
| 11 | + |
| 12 | +**错误位置**: 第 53-62 行的 `choices` 数组映射 |
| 13 | + |
| 14 | +**根本原因**: |
| 15 | + |
| 16 | +- `x.manifest.name` 的类型是 `string | undefined` |
| 17 | +- 当值为 `undefined` 时,直接赋值给 `description` 属性违反了 `exactOptionalPropertyTypes` 规则 |
| 18 | +- 该规则要求可选属性只能显式设置为其定义的类型,不能通过 `undefined` 值来表示"未设置" |
| 19 | + |
| 20 | +## 解决方案 |
| 21 | + |
| 22 | +### 修复前代码 |
| 23 | + |
| 24 | +```typescript |
| 25 | +cleanDirs = await checkbox<string>({ |
| 26 | + message: '请选择需要清理的目录', |
| 27 | + choices: filteredPackages.map((x) => { |
| 28 | + return { |
| 29 | + name: path.relative(workspaceDir, x.rootDir), |
| 30 | + value: x.rootDir, |
| 31 | + checked: true, |
| 32 | + description: x.manifest.name, // ❌ 类型错误:可能是 undefined |
| 33 | + } |
| 34 | + }), |
| 35 | +}) |
| 36 | +``` |
| 37 | + |
| 38 | +### 修复后代码 |
| 39 | + |
| 40 | +```typescript |
| 41 | +cleanDirs = await checkbox<string>({ |
| 42 | + message: '请选择需要清理的目录', |
| 43 | + choices: filteredPackages.map((x) => { |
| 44 | + const baseChoice = { |
| 45 | + name: path.relative(workspaceDir, x.rootDir), |
| 46 | + value: x.rootDir, |
| 47 | + checked: true, |
| 48 | + } |
| 49 | + // ✅ 只在有值时才添加 description 属性 |
| 50 | + return x.manifest.name |
| 51 | + ? { ...baseChoice, description: x.manifest.name } |
| 52 | + : baseChoice |
| 53 | + }), |
| 54 | +}) |
| 55 | +``` |
| 56 | + |
| 57 | +## 技术要点 |
| 58 | + |
| 59 | +### exactOptionalPropertyTypes 规则说明 |
| 60 | + |
| 61 | +这是 TypeScript 4.4+ 引入的严格类型检查选项,用于区分: |
| 62 | + |
| 63 | +- **未设置的属性** (属性不存在) |
| 64 | +- **设置为 undefined 的属性** (属性存在但值为 undefined) |
| 65 | + |
| 66 | +**示例**: |
| 67 | + |
| 68 | +```typescript |
| 69 | +interface Person { |
| 70 | + name: string |
| 71 | + age?: number // 可选属性 |
| 72 | +} |
| 73 | + |
| 74 | +// 启用 exactOptionalPropertyTypes 前 |
| 75 | +const p1: Person = { name: 'Alice', age: undefined } // ✅ 允许 |
| 76 | +const p2: Person = { name: 'Bob' } // ✅ 允许 |
| 77 | + |
| 78 | +// 启用 exactOptionalPropertyTypes 后 |
| 79 | +const p1: Person = { name: 'Alice', age: undefined } // ❌ 错误 |
| 80 | +const p2: Person = { name: 'Bob' } // ✅ 允许 |
| 81 | +``` |
| 82 | + |
| 83 | +### 修复策略 |
| 84 | + |
| 85 | +采用**条件对象展开**模式: |
| 86 | + |
| 87 | +1. 创建基础对象(包含必需属性) |
| 88 | +2. 根据条件动态添加可选属性 |
| 89 | +3. 使用对象展开运算符合并 |
| 90 | + |
| 91 | +**优点**: |
| 92 | + |
| 93 | +- 类型安全 |
| 94 | +- 代码简洁 |
| 95 | +- 符合 TypeScript 严格模式最佳实践 |
| 96 | + |
| 97 | +## 验证结果 |
| 98 | + |
| 99 | +### 构建验证 |
| 100 | + |
| 101 | +```bash |
| 102 | +$ pnpm --filter @icebreakers/monorepo build |
| 103 | +✔ Build complete in 1791ms (CJS) |
| 104 | +✔ Build complete in 1794ms (ESM) |
| 105 | +``` |
| 106 | + |
| 107 | +### 测试验证 |
| 108 | + |
| 109 | +```bash |
| 110 | +$ pnpm --filter @icebreakers/monorepo test |
| 111 | +✓ 34 个测试文件通过 |
| 112 | +✓ 102 个测试用例通过 |
| 113 | +Duration: 4.32s |
| 114 | +``` |
| 115 | + |
| 116 | +### 类型检查 |
| 117 | + |
| 118 | +- ✅ 无 TypeScript 编译错误 |
| 119 | +- ✅ 无 ESLint 错误 |
| 120 | +- ✅ 所有严格类型检查通过 |
| 121 | + |
| 122 | +## 影响范围 |
| 123 | + |
| 124 | +**修改的文件**: |
| 125 | + |
| 126 | +- `packages/monorepo/src/commands/clean.ts` (第 51-63 行) |
| 127 | + |
| 128 | +**影响的功能**: |
| 129 | + |
| 130 | +- `monorepo clean` 命令的交互式包选择功能 |
| 131 | +- 清理时的包列表显示 |
| 132 | + |
| 133 | +**向后兼容性**: |
| 134 | + |
| 135 | +- ✅ 完全兼容,无破坏性变更 |
| 136 | +- ✅ 功能行为保持不变 |
| 137 | +- ✅ API 接口保持不变 |
| 138 | + |
| 139 | +## 相关配置 |
| 140 | + |
| 141 | +**启用的 TypeScript 严格选项** (tsconfig.json): |
| 142 | + |
| 143 | +```json |
| 144 | +{ |
| 145 | + "compilerOptions": { |
| 146 | + "strict": true, |
| 147 | + "exactOptionalPropertyTypes": true, |
| 148 | + "noUncheckedIndexedAccess": true, |
| 149 | + "noPropertyAccessFromIndexSignature": true, |
| 150 | + "noImplicitOverride": true |
| 151 | + } |
| 152 | +} |
| 153 | +``` |
| 154 | + |
| 155 | +## 总结 |
| 156 | + |
| 157 | +通过采用条件对象展开模式,成功修复了 `exactOptionalPropertyTypes` 严格模式下的类型错误。修复后的代码更加类型安全,符合 TypeScript 最佳实践,同时保持了代码的可读性和简洁性。 |
| 158 | + |
| 159 | +这次修复也验证了项目在第一阶段优化中启用的严格 TypeScript 配置的有效性,确保了代码质量的提升。 |
0 commit comments