TypeScript 高级类型技术:泛型、联合和交叉类型

45 阅读5分钟

泛型 在 TypeScript 中,泛型是一个强大的工具,它允许我们编写能够适应多种类型的可重用组件。

  1. 通用约束 泛型可以限制为特定类型或接口,以确保传递给泛型的类型满足某些条件。例如,如果我们希望函数仅接受具有length属性的类型,我们可以这样做:

interface Lengthwise { length: number; }

function printLength(item: T): void { console.log(item.length); }

const stringLength = "hello"; printLength(stringLength); // OK, strings have a length property const objectWithoutLength = { name: "World" }; printLength(objectWithoutLength); // Error, no length property 确保T必须具有某个length属性。

  1. 使用泛型进行类型推断 TypeScript 允许在某些情况下自动推断泛型类型,尤其是在函数调用期间。例如,使用infer关键字,我们可以从函数类型中提取返回类型:

type ReturnType = T extends (...args: any[]) => infer R ? R : never;

function identity(arg: T): T { return arg; }

type IdentityReturnType = ReturnType; // IdentityReturnType is inferred as 'string' 这里,ReturnType从函数类型中提取返回类型。

  1. 多参数泛型 您可以定义接受多个通用参数的类型或函数:

interface Pair<T, U> { first: T; second: U; }

function createPair<T, U>(first: T, second: U): Pair<T, U> { return { first, second }; }

const pair = createPair("Hello", 42); // pair has type Pair<string, number> 该createPair函数接受两个通用参数T并U返回一个类型对象Pair<T, U>。

  1. 通用接口 接口也可以使用泛型:

interface GenericIdentityFn { (arg: T): T; }

function identity(arg: T): T { return arg; }

let myIdentity: GenericIdentityFn = identity; // myIdentity is a number-specific version of the identity function 这里,GenericIdentityFn是一个通用接口,并且identity函数被分配给它的一个实例,限制它只能处理number类型参数。

  1. 泛型类 类也可以定义为泛型,允许方法和属性使用不同的类型:

class Box { value: T;

constructor(value: T) { this.value = value; }

setValue(newValue: T): void { this.value = newValue; } }

const boxOfStrings = new Box("Hello"); boxOfStrings.setValue("World"); // OK boxOfStrings.setValue(123); // Error, incompatible types 该类Box接受泛型类型T,具体类型在实例化时指定。

联合类型 TypeScript 中的联合类型允许将多种类型组合为一种类型,这意味着一个变量可以是几种类型之一。

  1. 类型保护和类型断言 处理联合类型时,可能需要确定变量的具体类型。这可以通过类型保护来实现,类型保护是一些函数或表达式,用于检查变量的属性以缩小其可能的类型范围。例如:

type Shape = { kind: 'circle'; radius: number } | { kind: 'square'; side: number };

function getArea(shape: Shape): number { if ('radius' in shape) { // Type guard: shape is now { kind: 'circle'; radius: number } return Math.PI * shape.radius ** 2; } else { // Type guard: shape is now { kind: 'square'; side: number } return shape.side ** 2; } } 在这个例子中,检查是否shape具有radius属性可以确定它是圆形还是正方形。

2.非空断言运算符(!) 非空断言运算符!告知编译器,即使联合类型包含null或undefined,您也确定其值既不是 也不是 。但是,不正确的使用可能会导致运行时错误:

function logValue(value: string | null | undefined): void { if (value) { console.log(value!.toUpperCase()); // Use ! for non-null assertion } else { console.log('Value is null or undefined'); } } 这里,如果value不是null或undefined,我们使用!来抑制潜在的null或undefined输入警告。

3.分配类型运算符(&和|) 将联合类型或交集类型应用于泛型类型时,它们会分布在该泛型的每个实例上。例如,对于元素为联合类型的数组:

type NumbersOrStrings = number | string; type ArrayWithMixedElements = T[];

const mixedArray: ArrayWithMixedElements = [1, "two", 3]; mixedArray的元素类型是NumbersOrStrings,因此它可以包含number或string。

4.模式匹配 在解构赋值、函数参数或类型别名中,可以使用模式匹配来处理联合类型的值:

type Shape = { kind: 'circle'; radius: number } | { kind: 'square'; side: number };

function handleShape(shape: Shape) { switch (shape.kind) { case 'circle': const { radius } = shape; // Now we know shape is { kind: 'circle'; radius: number } break; case 'square': const { side } = shape; // Now we know shape is { kind: 'square'; side: number } break; } } 在这个例子中,该switch语句充当类型保护,根据kind属性处理不同的形状类型。

交叉口类型 TypeScript 中的交叉类型允许将多种类型组合成一种新类型,该新类型包含原始类型的所有属性和方法。

  1. 组合类型 交集类型使用&运算符来合并两个或多个类型。例如,假设我们有两个接口,Person和Employee。我们可以创建一个PersonAndEmployee交集类型:

interface Person { name: string; age: number; }

interface Employee { id: number; department: string; }

type PersonAndEmployee = Person & Employee;

const person: PersonAndEmployee = { name: 'Alice', age: 30, id: 123, department: 'HR', }; 该变量必须满足和接口www.ysdslt.com/person的要求。PersonEmployee

  1. 类和接口的交集 交叉类型也可以应用于类和接口之间,将类实例与接口的属性和方法结合起来:

class Animal { name: string; makeSound(): void { console.log('Making sound...'); } }

interface HasColor { color: string; }

class ColoredAnimal extends Animal implements HasColor { color: string; }

type ColoredAnimalIntersection = Animal & HasColor;

function describeAnimal(animal: ColoredAnimalIntersection) { console.log(The ${animal.name} is ${animal.color} and makes a sound.); animal.makeSound(); }

const coloredCat = new ColoredAnimal(); coloredCat.name = 'Kitty'; coloredCat.color = 'Gray'; describeAnimal(coloredCat); 该ColoredAnimalIntersection类型既是Animal类的实例,又具有接口color的属性HasColor。

  1. 类型保护 交叉类型在类型保护中很有用,尤其是在确定联合类型中的特定类型时。例如,你可能有一个对象,它可能是两种类型之一,并且你想在特定时刻确定它是哪一种:

interface Movable { move(): void; }

interface Static { stay(): void; }

type ObjectState = Movable & Static;

function isMovable(obj: ObjectState): obj is Movable { return typeof obj.move === 'function'; }

const object: ObjectState = { move: () => console.log('Moving...'), stay: () => console.log('Staying...') };

if (isMovable(object)) { object.move(); // Type guard ensures the move method exists } else { object.stay(); } 该isMovable函数是一个类型保护,用于检查该move方法是否存在,并确认其object类型为Movable。

📘 想要获得更多实用的编程教程?

👨‍💻 如果你想系统地学习前端、后端、算法、架构设计,我会在 Patreon 上持续更新内容包