一、字面量类型
在 TypeScript 中,字面量不仅可以表示值,还可以表示类型,即所谓的字面量类型。TypeScript 支持 3 种字面量类型:字符串字面量类型、数字字面量类型、布尔字面量类型。对应的字符串字面量、数字字面量、布尔字面量分别拥有与其值一样的字面量类型:
let str: 'hello world' = 'hello world';
let num: 996 = 996;
let bool: true = true
1. 字面量类型的使用
字符串字面量
字符串字面量类型其实就是字符串常量,与字符串类型不同的是它是具体的值:
type Name = "TS";
const name1: Name = "test"; // error 不能将类型"test"分配给类型"TS"
const name2: Name = "TS";
实际上,定义单个的字面量类型并没有太大用处,它的应用场景是可以把多个字面量类型组合成一个联合类型,用来描述拥有明确成员的实用的集合:
type Direction = "north" | "east" | "south" | "west";
function getDirectionFirstLetter(direction: Direction) {
return direction.substr(0, 1);
}
getDirectionFirstLetter("test"); // error 类型"test"的参数不能赋给类型“Direction”的参数
getDirectionFirstLetter("east");
这里我们使用四个字符串字面量类型组合成了一个联合类型,这样编译器就会检查我们使用的参数是否是指定的字面量类型集合中的成员。通过这种方式,可以将函数的参数限定为更具体的类型。这不仅提升了代码的可读性,还保证了函数的参数类型。
数字字面量
数字字面量类型和字符串字面量类型差不多,都是指定类型为具体的值:
type Age = 18;
interface Info {
name: string;
age: Age;
}
const info: Info = {
name: "TS",
age: 28 // error 不能将类型“28”分配给类型“18”
};
布尔字面量
布尔字面量和上面的两个类似,不在多说:
let success: true
let fail: false
let value: true | false
由于布尔值只有true和false两种,所以以下两种类型意思一样的:
let value: true | false
let value: boolean
2. 字面量类型的拓宽
在ES6中提出了两个新的声明变量的关键字:let和const,那当他们定义的变量的值相同时,变量的类型是一样的吗?
先来看使用const定义变量的例子:
const str = "hello world";
const num = 996;
const bool = false;
这里const定义了三个不能变的常量,在不写类型注解的情况下,TypeScript 会推断出它的类型为赋值字面量的类型。这样就不能再改变变量的值。
再来看使用let定义变量的例子:
let str = "hello world";
let num = 996;
let bool = false;
这里没有写注解的变量的类型就变成了赋值字面量类型的父类型,比如str的类型是字符串字面量类型"hello world"的父类型string,num的类型是数字字面量类型996的父类型number,bool的类型是布尔字面量类型false的父类型boolean。这样就意味着,我们可以给这三个变量分别赋值string、number、boolean类型的值:
str = "hello TypeScript";
num = 666;
bool = true;
这种将字面量类型转换为其父类型的设计就是字面量类型的拓宽。 通过 let 或 var 定义的变量、函数形参、对象的非只读属性,如果指定了初始值且未显式添加类型注解,那么它们推断出来的类型就是指定的初始值字面量类型拓宽后的类型,这就是字面量类型拓宽。
下面通过一个例子来理解一下字面量类型拓宽:
let str = 'hello'; // 类型是 string
let strFun = (str = 'hello') => str; // 类型是 (str?: string) => string;
const specifiedStr = 'hello'; // 类型是 'this is string'
let str2 = specifiedStr; // 类型是 'string'
let strFun2 = (str = specifiedStr) => str; // 类型是 (str?: string) => string;
第一段代码中通过let定义了字符串str,是一个形参,并且没有显式的声明其类型,属于是类型拓宽,所以变量和形参推断出类型为string。
第二段代码中通过const定义了字符串specifiedStr,这个字符串是常量,不能进行修改,所以specifiedStr的类型为hello字面量类型,后面的str2遍历和strFun2函数形参被赋值了字面量类型的常量,并且没有显式的声明其类型,所以变量、形参的类型都被拓宽了,并没有被指定为它对应的字面量类型。这也是符合我们预期的。
二、联合类型
1. 联合类型的使用
如果希望属性为多种类型之一,如字符串或者数组,这时联合类型就派上用场了(它使用 | 作为标记,如 string | number)。**联合类型可以理解为多个类型的并集。**联合类型用来表示变量、参数的类型不是某个单一的类型,而可能是多种不同的类型的组合:
function formatCommandline(command: string[] | string) {
let line = '';
if (typeof command === 'string') {
line = command.trim();
} else {
line = command.join(' ').trim();
}
}
联合类型表示一个值可以是几种类型之一,用竖线 | 分隔每个类型,所以 number | string | boolean 表示一个值可以是number、string、boolean类型中的任意一种。
可以使用类型别名抽离联合类型:
type Command = string[] | string
2. 类型缩减
说完了联合类型的基本使用,那如果定义的联合类型的包含数字类型和数字字面量类型这种情况,会有什么效果呢?实际上,由于数字类型是数字字面量类型的父类型,所以最后会缩减为数字类型。同样string和boolean在这种情况下也会发生类型缩减。
看下面的例子:
type UnionNum = 1 | number; // 类型是number
type UniomStr = "string" | string; // 类型是string
type UnionBool = false | boolean; // 类型是boolean
在这种情况下,TypeScript会对类型进行缩减,将字面量类型去掉,保留原始类型。
但是这样也会造成一个问题:编译器只能提示我们定义的变量是那个原始的类型:
不过,TypeScript提供了一种方式来控制类型缩减,只需给父类型添加"& {}"即可:
此时,其他字面量类型就不会被缩减,在编辑器中字符串字面量str1、str2等就可以自动提示出来了。
除此之外,当联合类型的成员是接口类型,并满足其中一个接口的属性是另一个接口属性的子集,这个属性也会进行类型缩减:
type UnionInterface = {
age: "18"
} | {
age: "18" | "25",
[key: string]: string;
}
由于 "18"是 "18" | "25"的子集,所以age属性的类型会变成 "18" | "25"。