给大家推荐一本书:《Effective TypeScript》
在《Effective TypeScript》的第四篇中,类型设计被视为构建健壮、可维护TypeScript代码的核心环节。作者Dan Vanderkam通过62个实践方法中的关键条款,深入剖析了如何通过类型设计提升代码的安全性和可读性。以下是对该篇章核心内容的提炼与扩展。
1. 倾向选择总是代表有效状态的类型
类型设计应优先反映业务逻辑中的有效状态,而非无效或中间状态。例如,在处理用户登录流程时,应定义User
类型为包含id
、name
等字段的完整对象,而非仅包含email
的临时状态。
实践建议:
- 使用
never
类型或联合类型(如User | null
)明确表示无效状态。 - 避免在类型中暴露内部实现细节(如
_isLoading
等私有字段)。
2. 宽进严出:输入宽容,输出严格
函数设计应遵循"宽进严出"原则,即接受尽可能宽泛的输入类型,但返回严格限定的输出类型。
例如,一个处理用户输入的函数应接受string | number
类型,但返回经过验证的string
类型。
实践建议:
- 使用泛型或条件类型动态调整输入/输出类型。
- 通过运行时校验(如
zod
库)确保输入符合预期。
3. 避免在文档中重复类型信息
类型声明本身即文档,应避免在注释中重复类型信息。例如,以下代码是冗余的:
js
/**
* @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
)区分状态。 - 对复杂状态,可结合
type
和interface
实现更灵活的设计。
6. 选择更精确的字符串类型的替代类型
避免直接使用string
类型,应优先选择更精确的替代类型。例如:
js
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
。
实践建议:
- 避免使用通用名称(如
Manager
、Handler
),除非它们是领域术语。 - 通过团队讨论统一术语,避免命名歧义。
9. 考虑加"烙印"实现名义类型
当需要区分本质相同但语义不同的类型时,可通过"烙印"(Brand)模式实现名义类型。例如:
js
type UserId = string & { __brand: 'UserId' };
function createUserId(id: string): UserId {
return id as UserId;
}
实践建议:
- 使用
zod
或io-ts
等库内置的名义类型支持。 - 对复杂场景,可结合TypeScript的
nominal typing
技巧(如私有字段)。
总结
类型设计是TypeScript开发的核心技能。通过遵循"有效状态优先""宽进严出""避免重复类型信息"等原则,开发者可以构建出更安全、更易维护的代码。同时,结合工具链(如类型生成、运行时校验)和领域驱动设计(DDD)思想,可以进一步提升类型设计的表达力和实用性。
《Effective TypeScript》的第四篇不仅提供了具体的实践方法,更传递了一种以类型为中心的编程思维。掌握这些技巧,将帮助开发者从"使用TypeScript"跃升至"精通TypeScript"。