有一个接口结构,它描述了响应的消息结构:
interface IRes {
code: number;
status: string;
data: any;
}
在大多数情况下,这里的 code 与 status 实际值会来自于一组确定值的集合,比如 code 可能是 10000 / 10001 / 50000,status 可能是 “success” / “failure”。
字面量类型与联合类型
我们可以使用联合类型加上字面量类型,把上面的例子改写成这样:
interface Res {
code: 10000 | 10001 | 50000;
status: "success" | "failure";
data: any;
}
字面量类型
在 TypeScript 中,像上面”success”这种叫做字面量类型(Literal Types),它代表着比原始类型更精确的类型,同时也是原始类型的子类型。
字面量类型主要包括字符串字面量类型、数字字面量类型、布尔字面量类型和对象字面量类型,它们可以直接作为类型标注。
单独使用字面量类型比较少见,因为单个字面量类型并没有什么实际意义。它通常和联合类型(即这里的 |)一起使用,表达一组字面量类型:
interface Tmp {
bool: true | false;
num: 1 | 2 | 3;
str: "lin" | "bu" | "du"
}
联合类型
而联合类型你可以理解为,它代表了一组类型的可用集合,只要最终赋值的类型属于联合类型的成员之一,就可以认为符合这个联合类型。联合类型对其成员并没有任何限制,除了上面这样对同一类型字面量的联合,我们还可以将各种类型混合到一起:
interface Tmp {
mixed: true | string | 599 | {} | (() => {}) | (1 | 2)
}
这里有几点需要注意的:
- 对于联合类型中的函数类型,需要使用括号()包裹起来
- 函数类型并不存在字面量类型,因此这里的 (() => {}) 就是一个合法的函数类型
- 你可以在联合类型中进一步嵌套联合类型,但这些嵌套的联合类型最终都会被展平到第一级中
联合类型的常用场景之一是通过多个对象类型的联合,来实现手动的互斥属性,即这一属性如果有字段1,那就没有字段2:
interface Tmp {
user:
| {
vip: true;
expires: string;
}
| {
vip: false;
promotion: string;
};
}
declare var tmp: Tmp;
if (tmp.user.vip) {
console.log(tmp.user.expires);
}
在这个例子中,user 属性会满足普通用户与 VIP 用户两种类型,这里 vip 属性的类型基于布尔字面量类型声明。我们在实际使用时可以通过判断此属性为 true ,确保接下来的类型推导都会将其类型收窄到 VIP 用户的类型(即联合类型的第一个分支)。这一能力的使用涉及类型守卫与类型控制流分析。
对象字面量类型
类似的,对象字面量类型就是一个对象类型的值。当然,这也就意味着这个对象的值全都为字面量值:
interface Tmp {
obj: {
name: "linbudu",
age: 18
}
}
const tmp: Tmp = {
obj: {
name: "linbudu",
age: 18
}
}
对象字面量类型在实际开发中的使用较少,我们只需要了解。
无论是原始类型还是对象类型的字面量类型,它们的本质都是类型而不是值。它们在编译时同样会被擦除,同时也是被存储在内存中的类型空间而非值空间。
枚举
枚举的用法
在JavaScript 中,我们声明下面一个对象:
const PageUrl = {
Home_Page_Url: "url1",
Setting_Page_Url: "url2",
Share_Page_Url: "url3",
}
使用枚举就可以这样写:
enum PageUrl {
Home_Page_Url = "url1",
Setting_Page_Url = "url2",
Share_Page_Url = "url3",
}
const home = PageUrl.Home_Page_Url;
这么做的好处非常明显。首先,你拥有了更好的类型提示。其次,这些常量被真正地约束在一个命名空间下(上面的对象声明总是差点意思)。如果你没有声明枚举的值,它会默认使用数字枚举,并且从 0 开始,以 1 递增:
enum Items {
Foo,
Bar,
Baz
}
在这个例子中,Items.Foo , Items.Bar , Items.Baz的值依次是 0,1,2 。
如果你只为某一个成员指定了枚举值,那么之前未赋值成员仍然会使用从 0 递增的方式,之后的成员则会开始从枚举值递增。
枚举是双向映射的,即你可以从枚举成员映射到枚举值,也可以从枚举值映射到枚举成员:
enum Items {
Foo,
Bar,
Baz
}
const fooValue = Items.Foo; // 0
const fooKey = Items[0]; // "Foo"