TypeScript 中的高级类型提供了更强大、更灵活的工具来处理复杂的类型系统。这些高级类型包括交叉类型、联合类型、条件类型、映射类型、索引类型查询以及类型别名等等。下面我们逐一介绍这些高级类型。
1. 交叉类型(Intersection Types)
交叉类型将多个类型合并为一个类型。使用 & 运算符来表示交叉类型。
interface Person {
name: string;
}
interface Employee {
employeeId: number;
}
type EmployeePerson = Person & Employee;
const employee: EmployeePerson = {
name: "John",
employeeId: 1234
};
在上述例子中,EmployeePerson 类型同时具有 Person 和 Employee 的所有属性。
2. 联合类型(Union Types)
联合类型表示可以是几种类型之一。使用 | 运算符来表示联合类型。
let value: string | number;
value = "Hello";
value = 42;
// value = true; // 错误:类型“true”不能赋值给类型“string | number”。
联合类型常用于函数参数中,允许传入多种类型的参数。
function format(input: string | number): string {
if (typeof input === "string") {
return input.toUpperCase();
} else {
return input.toFixed(2);
}
}
3. 类型别名(Type Aliases)
类型别名用来给类型起一个新的名字。使用 type 关键字来定义类型别名。
type Point = {
x: number;
y: number;
};
function printPoint(point: Point) {
console.log(`x: ${point.x}, y: ${point.y}`);
}
类型别名也可以是泛型的。
type Container<T> = { value: T };
let stringContainer: Container<string> = { value: "Hello" };
let numberContainer: Container<number> = { value: 42 };
4. 索引类型查询和索引访问(Index Types and Index Signatures)
使用索引类型查询操作符 keyof,可以获得某个类型的所有键组成的联合类型。
interface Person {
name: string;
age: number;
}
type PersonKeys = keyof Person; // "name" | "age"
索引访问操作符 T[K] 可以用来访问某个类型的属性类型。
type NameType = Person["name"]; // string
5. 映射类型(Mapped Types)
映射类型基于旧类型创建新类型。使用 in 关键字来遍历一个联合类型的所有成员。
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Partial<T> = {
[P in keyof T]?: T[P];
};
interface Person {
name: string;
age: number;
}
type ReadonlyPerson = Readonly<Person>;
type PartialPerson = Partial<Person>;
6. 条件类型(Conditional Types)
条件类型根据条件返回不同的类型。语法为 T extends U ? X : Y。
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
条件类型也可以嵌套和组合使用,非常强大。
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends undefined ? "undefined" :
T extends Function ? "function" :
"object";
type T0 = TypeName<string>; // "string"
type T1 = TypeName<number>; // "number"
type T2 = TypeName<() => void>; // "function"
type T3 = TypeName<{ a: number }>; // "object"
7. 内置工具类型(Utility Types)
TypeScript 提供了一些内置工具类型,用于常见的类型转换。
Partial<T>: 将类型T的所有属性变为可选。Required<T>: 将类型T的所有属性变为必选。Readonly<T>: 将类型T的所有属性变为只读。Record<K, T>: 创建一个以K为键,T为值类型的对象类型。Pick<T, K>: 从类型T中挑选出K属性。Omit<T, K>: 从类型T中排除K属性。
interface Todo {
title: string;
description: string;
completed: boolean;
}
type PartialTodo = Partial<Todo>;
type ReadonlyTodo = Readonly<Todo>;
type PickTodo = Pick<Todo, "title" | "completed">;
type OmitTodo = Omit<Todo, "description">;
8. 类型守卫和区分类型(Type Guards and Discriminated Unions)
类型守卫(Type Guards)
类型守卫是 TypeScript 中的一种表达式,用来在运行时检查类型,以确保代码按照预期的类型进行操作。常用的类型守卫包括 typeof、instanceof 和自定义的类型保护函数。
typeof
typeof 运算符用于检查原始类型。
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function printValue(value: string | number) {
if (isString(value)) {
console.log(`String: ${value.toUpperCase()}`);
} else {
console.log(`Number: ${value.toFixed(2)}`);
}
}
instanceof
instanceof 运算符用于检查对象的具体类型。
class Dog {
bark() {
console.log("Woof!");
}
}
class Cat {
meow() {
console.log("Meow!");
}
}
function makeSound(animal: Dog | Cat) {
if (animal instanceof Dog) {
animal.bark();
} else if (animal instanceof Cat) {
animal.meow();
}
}
- 自定义类型保护函数
可以通过创建返回布尔值的函数,并在其返回类型中使用类型谓词 value is Type 来实现更复杂的类型守卫。
interface Fish {
swim(): void;
}
interface Bird {
fly(): void;
}
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
function move(pet: Fish | Bird) {
if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}
}
区分联合(Discriminated Unions)
区分联合,又称“标签联合”或“代数数据类型”,是一种模式,它包含了一个共有的属性(通常称为"标签"或"标识")来区分多种可能的类型。
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
type Shape = Circle | Square;
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
}
}
const myCircle: Shape = { kind: "circle", radius: 10 };
const mySquare: Shape = { kind: "square", sideLength: 5 };
console.log(area(myCircle)); // 314.159...
console.log(area(mySquare)); // 25
在这个例子中,kind 属性用来区分 Shape 类型的不同变体。
9. 类型断言(Type Assertions)
类型断言可以告诉编译器某个值的确切类型,从而绕过编译器的类型推断。类型断言并不会在运行时改变值的类型,只是在编译时进行类型检查。类型断言有两种语法:
- 尖括号语法
let someValue: unknown = "this is a string";
let strLength: number = (<string>someValue).length;
as语法
let someValue: unknown = "this is a string";
let strLength: number = (someValue as string).length;
以下是一些使用类型断言的场景:
- 从
unknown或any类型转换
function processValue(value: unknown) {
if (typeof value === "string") {
console.log((value as string).toUpperCase());
}
}
- 处理 DOM 元素
const inputElement = document.getElementById("myInput") as HTMLInputElement;
inputElement.value = "Hello, World!";
- 避免使用非空断言
有时你知道某个值不会是 null 或 undefined,但编译器不确定,可以使用类型断言:
const element = document.querySelector(".my-class") as HTMLElement;
element.style.color = "red";
需要注意的是,类型断言应谨慎使用。错误的类型断言可能会导致运行时错误,因为它绕过了 TypeScript 的类型检查机制。
高级类型使得 TypeScript 的类型系统更加灵活和强大,可以满足各种复杂的类型需求。通过合理使用这些高级类型,可以大大提高代码的可维护性和类型安全性。