Typescript - 9. 高级类型

109 阅读5分钟

TypeScript 中的高级类型提供了更强大、更灵活的工具来处理复杂的类型系统。这些高级类型包括交叉类型、联合类型、条件类型、映射类型、索引类型查询以及类型别名等等。下面我们逐一介绍这些高级类型。

1. 交叉类型(Intersection Types)

交叉类型将多个类型合并为一个类型。使用 & 运算符来表示交叉类型。

interface Person {
    name: string;
}

interface Employee {
    employeeId: number;
}

type EmployeePerson = Person & Employee;

const employee: EmployeePerson = {
    name: "John",
    employeeId: 1234
};

在上述例子中,EmployeePerson 类型同时具有 PersonEmployee 的所有属性。

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 中的一种表达式,用来在运行时检查类型,以确保代码按照预期的类型进行操作。常用的类型守卫包括 typeofinstanceof 和自定义的类型保护函数。

  1. 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)}`);
    }
}
  1. 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();
    }
}
  1. 自定义类型保护函数

可以通过创建返回布尔值的函数,并在其返回类型中使用类型谓词 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)

类型断言可以告诉编译器某个值的确切类型,从而绕过编译器的类型推断。类型断言并不会在运行时改变值的类型,只是在编译时进行类型检查。类型断言有两种语法:

  1. 尖括号语法
let someValue: unknown = "this is a string";
let strLength: number = (<string>someValue).length;
  1. as 语法
let someValue: unknown = "this is a string";
let strLength: number = (someValue as string).length;

以下是一些使用类型断言的场景:

  • unknownany 类型转换
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!";
  • 避免使用非空断言

有时你知道某个值不会是 nullundefined,但编译器不确定,可以使用类型断言:

const element = document.querySelector(".my-class") as HTMLElement;
element.style.color = "red";

需要注意的是,类型断言应谨慎使用。错误的类型断言可能会导致运行时错误,因为它绕过了 TypeScript 的类型检查机制。

高级类型使得 TypeScript 的类型系统更加灵活和强大,可以满足各种复杂的类型需求。通过合理使用这些高级类型,可以大大提高代码的可维护性和类型安全性。