类型安全
反模式指的是当存在更好的替代方案时,却采用了常见的、低效的设计。基本类型偏执是一种反模式,它指的是用基础类型表示很多概念。例如用 number 类型表示邮编,用 string 表示电话号码等。
基本类型偏执的处理方法主要体现在对类型进行抽象。针对邮编来说:
function generateCode(code: Code) {}
function generateCode(code: number) {}
显然第一种函数签名更好,在代码提示和编译时,更容易校验错误。
Code 类型可以采用如下方式定义:
declare const CodeType: unique symbol
class Code {
readonly value: number
[CodeType]: void
constructor(value: number) {
this.value = value
}
}
上述代码的关键点是 declare const CodeType: unique symbol 和 [CodeType]: void,这样可以避免 TypeScript 将具有相同形状的类型解释为 Code 类型。
通过定义更具意义的 Code 类型,我们可以避免基本类型偏执的陷阱,但并没有对类型起到更严格的约束,因为 Code 类型关注的是类型与意义的关系。
类型除了具有意义,还需要进行范围的约束。例如邮编应该是不超过6位的数字。
解决约束问题可以通过构造函数进行处理:
declare const CodeType: unique symbol
class Code {
readonly value: number
[CodeType]: void
constructor(value: number) {
if(value >= 100000) throw new Error()
this.value = value
}
}
除了通过构造函数外,我们也可以通过 getter 方法进行约束。
一般来说构造函数不应该做很复杂的事情,在处理复杂的范围计算问题时,可以通过工厂函数来进行约束
declare const CodeType: unique symbol
class Code {
readonly value: number
[CodeType]: void
constructor(value: number) {
this.value = value
}
static makeCode(value: number): Code {
if(value >= 100000) throw new Error()
return new Code(value)
}
}
何时使用类型转换
在联合类型中,可能会使用到类型转换,因为在这种情况下,我们知道的比编译器更多。
type Left = 'left'
type Right = 'right'
// 函数返回联合类型
function getDirection(angle: number): Left | Right | undefined {
if (angle === 180) return 'left'
if (angle === 0) return 'right'
return undefined
}
// 使用类型转换
const result = getDirection(180) // 类型是 Left | Right | undefined
if (result === 'left') {
// 通过运行时检查后,使用类型断言进行类型转换
const leftValue: Left = result as Left // 类型转换:Left | Right | undefined → Left
console.log(leftValue) // 'left'
}
使用 unknown 强制类型转换
当两个类型形状完全不同,无法直接转换时,需要借助 unknown 作为中间类型。
unknown 是 TypeScript 的顶层类型,可以安全地转换为任何类型。通过 as unknown as TargetType 的方式,我们可以绕过 TypeScript 的类型检查,进行强制类型转换。
// API 返回的原始数据(字符串)
type ApiResponse = string
// 我们期望的数据结构(对象)
type UserData = {
name: string
age: number
}
function fetchUserData(): ApiResponse {
return '{"name": "Alice", "age": 30}' // 实际返回 JSON 字符串
}
// 更实际的用法:解析 JSON 字符串
function parseUserData(response: ApiResponse): UserData {
const parsed = JSON.parse(response) // parsed 的类型是 any
// 使用 unknown 进行类型转换,确保类型安全
return parsed as unknown as UserData
}
向上和向下类型转换
将子类型转为父类型是一种常见的转换操作,TypeScript 中可以隐式实现。
但父类型向子类型转换并不一定安全。