在 TypeScript 中,类型别名起到的就是变量的作用,它可以存储一个类型,后续你可以直接引用它即可。

1
2
3
4
type Handler = () => void

const handler1: Handler = () => {}
const handler2: Handler = () => {}

也可以使用类型别名来替换接口,实现对对象类型的复用:

1
2
3
4
5
6
7
8
9
10
type User = {
userName: string
userAge: number
userMarried: boolean
userJob?: string
}

const user: User = {
/* ... */
}

实际上,类型别名也可以像函数一样接受参数并返回计算结果(例如:泛型),
在类型层面,或逻辑可以由联合类型实现,与逻辑可以由交叉类型实现。

联合类型

语法:A | B |C

1
type PossibleTypes = string | number | boolean

它所表示的或逻辑,只要你的变量满足其中一个类型成员,就可以被认为满足这个类型,因此你的变量可以在后续被赋值为其它的类型成员:

1
2
3
4
let foo: PossibleTypes = 'jude'

foo = 599
foo = true

联合类型对其中的类型成员并没有限制,你可以混合原始类型,字面量类型,函数类型,对象类型等等。在实际应用中,最常见的应该是字面量联合类型,它表示一组精确的字面量类型:

1
2
type Status = 'success' | 'failure'
type Code = 200 | 404 | 500

字面量类型

字面量类型是什么?先类比到原始类型 string,我们知道被标记为 string 类型的变量只能被赋值为字符串,换句话说,所有的字符串值都属于 string 类型。那么这就显得过于宽泛了,如果我们希望将变量类型约束在几个特定的字符串值之间呢?就比如上面的类型别名 Status,就能表达“这个变量是字符串类型”和“这个变量只能是’success’和’failure’两个字符串”这两个概念。而组成 Status 的这两个“值”,其实就是字面量类型,比如你也可以用字面量类型来作为类型标注:

1
2
const fixedStr: 'jude' = 'jude' // 值只能是 'jude'
const fixedNum: 599 = 599 // 值只能是 599

如果你感觉字面量类型和实际值不好区分,其实只要注意它们出现的位置即可,一个同样的字符串,只要出现在类型标注的位置,那指的当然就是类型啦。

字面量类型是和原始类型以及对象类型对应的——是的,包括对象类型,我们来看完整的示例:

1
2
3
4
5
const literalString: 'jude' = 'jude'
const literalNumber: 599 = 599
const literalBoolean: true = true
const literalObject: { name: 'jude' } = { name: 'jude' }
const literalArray: [1, 2, 3] = [1, 2, 3]

为什么需要字面量类型?

因为字面量联合类型相比它们对应的原始类型,能够提供更精确的类型信息与类型提示

除了基于字面量类型的小范围精确标注,我们也可以使用由接口组成的联合类型:

1
2
3
4
5
6
7
8
9
10
interface VisitorUser {}
interface CommonUser {}
interface VIPUser {}
interface AdminUser {}

type User = VisitorUser | CommonUser | VIPUser | AdminUser

const user: User = {
// ...任意实现一个组成的对象类型
}

联合类型和交叉类型

既然能够将联合类型关联到按位或,那么从按位与逻辑到交叉类型就更好理解了,类似于按位或 || 到联合类型的 |,交叉类型的 & 也脱胎自按位与 &&,我们同样可以使用类型别名来表示一个交叉类型

1
2
3
4
interface UserBasicInfo {}
interface UserJobInfo {}
interface UserFamilyInfo {}
interface UsUserBasicInfo & UserJobInfo & UserFamilyInfo;

类型别名 UserInfo 表示,你需要实现 UserBasicInfo、UserJobInfo、UserFamilyInfo 这三个对象类型的所有属性,才能认为是实现了 UserInfo 类型。交叉类型的本质,其实就是表示一个同时满足这些子类型成员的类型,所以如果你交叉两个对象类型,可以理解为是一个新的类型内部合并了这两个对象类型。

交叉原始类型会怎样?

1
type Test = string & number // never 类型

交叉类型的关键点:同时满足

联合类型和交叉类型可同时使用:

1
2
3
4
// 伪代码  先交叉再联合:只要实现任意一个交叉类型即可
type User = (FE & React & Vue) | (Sing & Rap)
// 先联合再交叉:只要这些联合类型存在交集,交集中的类型就可以认为是同时实现了所有联合类型。
type Union = (1 | 2 | 3) & (1 | 2) // 1|2