Combining Types
TypeScript的类型系统允许用户使用各种运算符从现有类型构建新类型。常见的包含:联合类型、交叉类型、类型别名等。
联合类型
联合类型允许为一个变量或参数明确多个可能的类型,表示其值的类型可能是类型列表的之一,可以很好的避免类型转换的问题,通过|操作符定义。
function printId(id: number | string) {
console.log(`Your ID is: ${id}`)
}
printId(100)
printId('200')
// Argument of type '{ myID: number; }' is not assignable to parameter of type 'string | number'.
printId({ myID: 22342 })
如上,printId函数参数是number或string类型,其它类则会报错。
提供一个匹配联合类型的值很简单,只需提供与联合类型成员匹配的类型即可。但是在使用时有一点要注意:只有当联合中每个成员都合法时,TypeScript才允许进行操作。如下示例,toUpperCase方法只对string类型是有效的,因此无法匹配:
function printId(id: number | string) {
// Property 'toUpperCase' does not exist on type 'string | number'.Property 'toUpperCase' does not exist on type 'number'.
console.log(id.toUpperCase());
}
对于这个问题,解决办法是进行类型缩小,以明确什么类型可以进行什么样的操作。当TypeScript能够根据代码结构推导出更具体的类型时,便会发生类型缩小:
function printId(id: number | string) {
if (typeof id === 'string') {
console.log(id.toUpperCase());
} else {
console.log(id);
}
}
有时遇到一个联合类型,其中所有成员都具有共同点,此时可以不进行类型缩小:
// array and string both have slice.
function getFirstThree(x: number[] | string) {
return x.slice(0, 3);
}
对于对象类型的联合类型,其值不一定是联合中的某个特定成员,而是可以同时是两个成员:
interface Dog {
name: string;
age: number;
}
interface Cat {
name: string;
breed: string;
}
// only Dog
const animal1: Dog | Cat = {
name: 'animal',
age: 13,
}
// only Cat
const animal2: Dog | Cat = {
name: 'animal',
breed: 'dog'
}
// Both of them
const animal3: Dog | Cat = {
name: 'animal',
age: 13,
breed: 'dog'
}
联合类型检查用于函数的返回值:
function returnNumberOrString(a: number, b: string): number | string {
return a || b
}
交叉类型
接口允许我们通过扩展其他类型来构建新类型。TypeScript 提供了另一种构造称为交叉类型,主要用于组合现有的对象类型,使用& 运算符进行定义。
interface Colorful {
color: string;
}
interface Circle {
radius: number;
}
const circle: Colorful & Circle = {
color: "red",
radius: 42
}
交叉类型的值必须同时满足所有类型的要求,而联合类型的值只需要满足其中一个类型的要求。
| 特性 | 交叉类型 | 联合类型 |
|---|---|---|
| 含义 | 同时具有多个类型的特性 | 具有多个类型中任意一个类型的特性 |
| 表示方式 | 使用&符号连接多个类型 | 使用|符号连接多个类型 |
| 特点 | 交叉类型的值必须同时满足所有类型的要求 | 联合类型的值只需要满足其中一个类型的要求 |
| 使用场景 | 用于组合多个类型,例如将多个接口合并成一个接口 | 用于表示一个值可以是多个类型中的任意一个类型 |
当使用交叉类型时,其类型顺序不重要,结果都是相同的属性:
// typeAB and typeBA have the same properties.
type typeAB = typeA & typeB;
type typeBA = typeB & typeA;
类型别名
类型别名简单的说就是一种类型的另一个名称,首字母通常大写(大驼峰),推荐在类型名成前加上Type。
type Point = {
x: number;
y: number;
}
function printPoint(p: Point) {
console.log(`${p.x}, ${p.y}`);
}
printPoint({ x: 12, y: 26 });
可以使用类型别名为任何类型命名,不仅限于对象类型。例如,类型别名可以命名一个联合类型、交叉类型:
type ID = string | number;
type Dog = { name: string } & { age: number };
类型别名只是别名,不能使用类型别名来创建同一类型的不同/不同版本,其效果就像直接编写了被命名的类型一样:
type UserInputSanitizedString = string;
function sanitizeInput(str: string): UserInputSanitizedString {
return 'sanitized string';
}
let userInput = sanitizeInput('user input');
userInput = "new input";
类型别名 vs 接口
类型别名和接口非常地相似,接口几乎所有的特性类型别名都可用,很多情况下可以选择它们其中之一。它们的主要区别是类型别名被定义后不能添加新的属性,而接口通常是可拓展的,定义后可添加属性。
基本类型、数组、元组、联合类型、函数等通常是类型别名。
对象、类接口通常使用接口。