Skip to main content

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函数参数是numberstring类型,其它类则会报错。

提供一个匹配联合类型的值很简单,只需提供与联合类型成员匹配的类型即可。但是在使用时有一点要注意:只有当联合中每个成员都合法时,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 接口

类型别名和接口非常地相似,接口几乎所有的特性类型别名都可用,很多情况下可以选择它们其中之一。它们的主要区别是类型别名被定义后不能添加新的属性,而接口通常是可拓展的,定义后可添加属性。

基本类型、数组、元组、联合类型、函数等通常是类型别名。

对象、类接口通常使用接口。