TypeScript高级类型技巧:泛型、联合与交叉类型

泛型

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

1. 泛型约束(Generic Constraints)

泛型可以被约束在一个特定的类型或类型接口上,确保传递给泛型的类型满足一定的条件。例如,如果我们希望一个函数只接收具有 length 属性的类型,我们可以这样做:

interface Lengthwise {
  length: number;
}

function printLength<T extends Lengthwise>(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 extends Lengthwise> 确保 T 必须具有 length 属性。

2. 类型推断(Type Inference with Generics)

TypeScript允许在某些情况下自动推断泛型的类型,特别是在函数调用时。例如,使用 infer

关键字可以从函数类型中提取返回值类型:

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

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

type IdentityReturnType = ReturnType<typeof identity>; // IdentityReturnType is inferred as 'string'

在这里,ReturnType 会从函数类型中提取返回值类型。

3. 多参数泛型(Multi-Parameter Generics)

可以定义接受多个泛型参数的类型或函数:

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 函数接受两个泛型参数 TU,并返回一个 Pair<T, U> 类型的对象。

4. 泛型接口(Generic Interfaces)

接口也可以使用泛型:

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

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

let myIdentity: GenericIdentityFn<number> = identity; // myIdentity is a number-specific version of the identity function

这里,GenericIdentityFn 是一个泛型接口,identity 函数被赋值给它的一个实例,限制了它只能处理 number 类型的参数。

5. 泛型类(Generic Classes)

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

class Box<T> {
  value: T;

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

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

const boxOfStrings = new Box<string>("Hello");
boxOfStrings.setValue("World"); // OK
boxOfStrings.setValue(123); // Error, incompatible types

Box 类接受一个泛型类型 T,并在实例化时指定具体类型。

联合

TypeScript 中的联合类型(Union Types)允许我们将多种类型合并成一个新的类型,这意味着一个变量可以是多种类型中的一种。

1. 类型保护与类型断言(Type Guards)

当你处理联合类型时,有时需要确定变量的具体类型。这可以通过类型保护来实现,即编写一个函数或表达式来检查变量的性质,从而缩小其可能的类型范围。例如:

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

function getArea(shape: Shape): number {
  if ('radius' in shape) {
    // 类型保护:现在我们知道 shape 是 { kind: 'circle'; radius: number }
    return Math.PI * shape.radius ** 2;
  } else {
    // 类型保护:现在我们知道 shape 是 { kind: 'square'; side: number }
    return shape.side ** 2;
  }
}

在这个例子中,通过检查 shape 是否有 radius 属性,我们能确定它是一个圆还是一个正方形。

2. 非空断言操作符(Non-null assertion operator, !)

非空断言操作符 ! 可以用来告诉编译器,即使联合类型中可能包含 nullundefined,你确定这个值不会是 null 或 undefined。但是,如果判断错误,运行时可能会抛出错误:

function logValue(value: string | null | undefined): void {
  if (value) {
    console.log(value!.toUpperCase()); // 使用 ! 进行非空断言
  } else {
    console.log('Value is null or undefined');
  }
}

在这里,如果 value 不是 nullundefined,我们使用 ! 来忽略潜在的 nullundefined 类型警告。

3. 类型拆解(Distributive Type Operator, & 和 |)

当你对一个泛型类型应用联合或交叉类型操作时,它们会“拆解”到泛型的每一个实例上。例如,对于一个数组的元素类型是联合类型的情况:

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

const mixedArray: ArrayWithMixedElements<NumbersOrStrings> = [1, "two", 3];

mixedArray 的元素类型是 NumbersOrStrings 的每一个实例,所以它可以包含 numberstring

4. 模式匹配(Pattern Matching)

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

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

function handleShape(shape: Shape) {
  switch (shape.kind) {
    case 'circle':
      const { radius } = shape;
      // 现在我们知道了 shape 是 { kind: 'circle'; radius: number }
      break;
    case 'square':
      const { side } = shape;
      // 现在我们知道了 shape 是 { kind: 'square'; side: number }
      break;
  }
}

在这个例子中,switch 语句作为类型保护,根据 kind 属性来处理不同类型的形状。

交叉类型

交叉类型(Intersection Types)在 TypeScript 中允许你将多个类型合并成一个新类型,这个新类型包含了所有原始类型的属性和方法。

1. 类型合并(Combining Types)

交叉类型使用 & 运算符来合并两个或更多类型。例如,假设我们有两个接口 PersonEmployee,我们可以创建一个 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',
};

person 变量现在必须同时符合 PersonEmployee 接口的要求。

2. 类与接口的交叉

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

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 类的实例,也拥有 HasColor 接口的 color 属性。

3. 类型防护(Type Guard)

交叉类型在类型防护中也很有用,尤其是当你需要在联合类型中确定一个特定的类型。例如,你可能有一个对象,它可能是两种类型之一,但你希望在某个时刻确定它是哪一种:

typescript
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(); // 类型防护确保了 move 方法存在
} else {
  object.stay();
}

isMovable 函数是一个类型保护,它检查 move 方法是否存在,如果存在,则表明 object 实际上是 Movable 类型。

2500G计算机入门到高级架构师开发资料超级大礼包免费送!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天涯学馆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值