TypeScript 类型设计——构建健壮代码的基石

41 阅读4分钟

给大家推荐一本书:《Effective TypeScript》

在《Effective TypeScript》的第四篇中,类型设计被视为构建健壮、可维护TypeScript代码的核心环节。作者Dan Vanderkam通过62个实践方法中的关键条款,深入剖析了如何通过类型设计提升代码的安全性和可读性。以下是对该篇章核心内容的提炼与扩展。

1. 倾向选择总是代表有效状态的类型

类型设计应优先反映业务逻辑中的有效状态,而非无效或中间状态。例如,在处理用户登录流程时,应定义User类型为包含idname等字段的完整对象,而非仅包含email的临时状态。
实践建议

  • 使用never类型或联合类型(如User | null)明确表示无效状态。
  • 避免在类型中暴露内部实现细节(如_isLoading等私有字段)。

2. 宽进严出:输入宽容,输出严格

函数设计应遵循“宽进严出”原则,即接受尽可能宽泛的输入类型,但返回严格限定的输出类型。
例如,一个处理用户输入的函数应接受string | number类型,但返回经过验证的string类型。

实践建议

  • 使用泛型或条件类型动态调整输入/输出类型。
  • 通过运行时校验(如zod库)确保输入符合预期。

3. 避免在文档中重复类型信息

类型声明本身即文档,应避免在注释中重复类型信息。例如,以下代码是冗余的:

/**
 * @param {string} name - 用户名称
 */
function greet(name: string) { ... }

实践建议

  • 使用TSDoc注释描述类型无法表达的业务逻辑(如参数约束)。
  • 对复杂类型,可通过类型别名或接口提升可读性。

4. 将空值推到类型边界

空值(null/undefined)应作为类型的边界条件处理,而非混入核心逻辑。例如,一个返回用户信息的函数应明确返回User | null,而非在内部处理空值。

实践建议

  • 使用可选链操作符(?.)和空值合并操作符(??)简化空值处理。
  • 对可能为空的字段,使用Partial<T>或自定义工具类型(如Nullable<T>)。

5. 优选接口的联合,而非联合的接口

当需要表示多个可能的状态时,优先使用接口的联合(如type State = Loading | Success | Error),而非联合的接口(如interface LoadingState { ... })。

实践建议

  • 使用discriminated union(可辨识联合)通过共同字段(如type)区分状态。
  • 对复杂状态,可结合typeinterface实现更灵活的设计。

6. 选择更精确的字符串类型的替代类型

避免直接使用string类型,应优先选择更精确的替代类型。例如:

type Email = string; // 不推荐

type Email = `${string}@${string}.${string}`; // 更精确(需TypeScript 4.1+)

实践建议

  • 使用字符串字面量类型(如type Status = 'active' | 'inactive')。
  • 对复杂模式,可结合正则表达式或第三方库(如io-ts)进行验证。

7. 从API和规范生成类型

类型应直接从API文档或规范生成,而非手动编写。例如,通过openapi-typescript等工具从OpenAPI规范生成类型。

实践建议

  • 使用d.ts文件或@types包管理第三方库的类型。
  • 对自定义API,编写类型生成脚本(如基于JSON Schema)。

8. 使用问题域语言命名类型

类型名称应直接反映业务领域概念,而非技术实现。例如,将UserList改为TeamMembers,将DataStore改为CustomerDatabase

实践建议

  • 避免使用通用名称(如ManagerHandler),除非它们是领域术语。
  • 通过团队讨论统一术语,避免命名歧义。

9. 考虑加“烙印”实现名义类型

当需要区分本质相同但语义不同的类型时,可通过“烙印”(Brand)模式实现名义类型。例如:

type UserId = string & { __brand: 'UserId' };
function createUserId(id: string): UserId {
 return id as UserId;
}

实践建议

  • 使用zodio-ts等库内置的名义类型支持。
  • 对复杂场景,可结合TypeScript的nominal typing技巧(如私有字段)。

总结

类型设计是TypeScript开发的核心技能。通过遵循“有效状态优先”“宽进严出”“避免重复类型信息”等原则,开发者可以构建出更安全、更易维护的代码。同时,结合工具链(如类型生成、运行时校验)和领域驱动设计(DDD)思想,可以进一步提升类型设计的表达力和实用性。

《Effective TypeScript》的第四篇不仅提供了具体的实践方法,更传递了一种以类型为中心的编程思维。掌握这些技巧,将帮助开发者从“使用TypeScript”跃升至“精通TypeScript”。