typescript 泛型

1,介绍

泛型(Generics)是 TypeScript 的一项强大的语言特性,它允许我们编写可以重用的代码,从而提高代码的可重用性、可维护性和灵活性。

简单来说,泛型就是将类型作为参数来定义一种通用的类型或函数的能力。通过泛型,我们可以定义一种函数、类或接口,其具体的类型是在使用时确定的,而不是在定义时确定的。这使得我们能够编写更灵活、更通用的代码,而不需要针对每种具体类型编写重复的代码。

简单的泛型函数示例:

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

函数 identity 接收一个泛型参数 T,表示输入的参数类型和返回值的类型。
在调用该函数时,我们可以使用不同的类型参数来调用该函数,从而得到不同的输出结果。例如:

// 不同的返回值类型
let output1 = identity<string>("Hello, world!");
let output2 = identity<number>(42);

除了函数,还可以使用泛型来定义类和接口。例如,下面是一个使用泛型的简单类示例:

class Box<T> {
  constructor(value: T) {
    this.contents = value;
  }
  contents: T;
}

Box类接收一个泛型类型参数 T,并在类中使用该参数来定义 contents 属性的类型。在类的构造函数中,我们可以将泛型类型参数 T 传递给构造函数,并将其赋值给 contents 属性。

let box1 = new Box<string>("Hello, world!");
let box2 = new Box<number>(42);

2,自定义泛型类型

在 TypeScript 中,使用 <> 来定义泛型类型参数,并在函数、类、接口或类型别名的定义中使用它们。

例如,下面的 Pair 类型表示一个由两个元素组成的键值对,其中键和值可以是任意类型:

type Pair<K, V> = {
  key: K;
  value: V;
};

在上面的代码中,使用了2个泛型类型参数 K 和 V 来表示键和值的类型。

通过定义这个泛型类型,得到一个类型安全的键值对,其中键和值可以是任意类型。

const person: Pair<string, number> = {
  key: "age",
  value: 30,
};

示例2

type Result<T> = {
  success: boolean;
  data: T;
};

function getResult<T>(data: T): Result<T> {
  return { success: true, data };
}

在上面的代码中,我们使用一个泛型类型参数 T 来表示 Result 类型中的 data 属性的类型。

getResult 函数可以接受任何类型的参数,并返回一个包含 success 和 data 属性的对象。

3,泛型约束

在 TypeScript 中,泛型约束是一种非常有用的特性,可以对泛型类型参数进行限制,从而增强代码的类型安全性和可读性。

可以使用下面的关键字实现泛型约束。(extends, keyof,infer最常见)

1,extends

通过使用 extends 关键字,可以对泛型类型参数进行更精确的限制,使得在使用泛型时获得更准确的类型推断和类型检查。

使用 extends 关键字来指定泛型类型参数时,泛型类型参数有2个特点

  1. 必须是某个类型的子类型
  2. 或者必须满足某个接口的约束条件。

举例,必须是某个类型的子类型:

function printLength<T extends Array<any>>(arr: T): void {
  console.log(arr.length);
}

printLength([1, 2, 3]);        // 输出 3
printLength(["a", "b", "c"]);  // 输出 3
printLength(123);              // 类型错误,必须是数组类型

在上面的示例中,定义了一个泛型函数 printLength,它接受一个数组类型的参数 arr
而使用 extends Array 泛型约束,我们限制了泛型类型参数 T 必须是 Array 的子类型。这意味着 arr 参数必须是一个数组类型,否则会产生类型错误

举例,必须满足某个接口的约束条件:

interface HasLength {
  length: number;
}

function printLength<T extends HasLength>(obj: T): void {
  console.log(obj.length);
}

printLength([1, 2, 3]);        // 输出 3
printLength("hello");          // 输出 5
printLength({ length: 10 });   // 输出 10
printLength(123);              // 类型错误,必须满足 HasLength 接口的约束条件

在上面的示例中,定义了一个接口 HasLength,它包含一个 length 属性。

然后又定义了一个泛型函数 printLength,它接受一个满足 HasLength 接口约束的参数 obj。

使用 extends HasLength 泛型约束,我们限制了泛型类型参数 T 必须满足 HasLength 接口的约束条件,即必须包含 length 属性。只有满足约束条件的参数才能通过类型检查。

2,keyof

keyof 是 TypeScript 中的一个索引类型查询操作符,用于获取一个类型的所有属性名组成的联合类型。它可以用于限制泛型类型参数只能是某个对象的属性名。

也就是说,keyof T 表示获取类型 T 的所有属性名组成的联合类型

type Person = {
  name: string;
  age: number;
};

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const person: Person = {
  name: "John",
  age: 30,
};

const name = getProperty(person, "name"); // 类型为 string
const age = getProperty(person, "age");   // 类型为 number

3,infer

在 TypeScript 中,infer 是一个用于条件类型(Conditional Types)中的关键字。

它用于从一个类型表达式中推断出一个新的类型,并将其作为条件类型的结果。例如,infer U 表示从某个类型中推断出一个新类型 U

infer 与条件类型结合使用时,可以从泛型类型参数中捕获具体的类型信息,然后将其赋值给一个新的类型变量。这允许我们在定义泛型类型时根据输入类型推断出其他类型的值,实现更灵活和复杂的类型操作。

示例1:提取函数返回类型:

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

function greet(): string {
  return "Hello, world!";
}

type Greeting = ReturnType<typeof greet>; // 类型为 string

在这个示例中,我们定义了一个 ReturnType 类型,它接受一个函数类型 T 作为参数。使用条件类型 T extends (...args: any[]) => infer R ? R : never,我们检查泛型类型参数 T 是否是一个函数类型,如果是的话,就将函数返回值类型 R 赋值给 infer R,否则返回 never 类型。

换句话说,假设原来的函数返回值类型是 x,则infer R表示:使用 infer关键字将 x 捕获并赋值给泛型变量 R。

通过使用 typeof greet 来获取 greet 函数的类型,并将这个类型作为泛型参数传递给 ReturnType 类型,我们可以获得 greet 函数的返回值类型,即 string。

示例2:提取数组元素类型:

type ElementType<T> = T extends Array<infer E> ? E : never;

type Numbers = [1, 2, 3, 4, 5];
type Element = ElementType<Numbers>; // 类型为 1 | 2 | 3 | 4 | 5

在这个示例中,我们定义了一个 ElementType 类型,它接受一个数组类型 T 作为参数。

使用条件类型 T extends Array<infer E> ? E : never,我们检查泛型类型参数 T 是否是一个数组类型,如果是的话,就将数组元素类型 E 赋值给 infer E,否则返回 never 类型。

通过将数组类型[1, 2, 3, 4, 5]作为泛型参数传递给 ElementType 类型,我们可以推断出数组元素的类型为 1 | 2 | 3 | 4 | 5

4,Partial

用于将一个类型的所有属性变为可选属性。例如,Partial<T> 表示将 T 类型的所有属性变为可选属性。

举例,

type Person = {
  name: string;
  age: number;
};

function updatePerson(person: Person, update: Partial<Person>): void {
  Object.assign(person, update);
}

const person: Person = {
  name: "John",
  age: 30,
};

updatePerson(person, { name: "Alice" }); // 第2个参数可以只传 name 属性

5,Required

用于将一个类型的所有属性变为必选属性。例如,Required<T> 表示将 T 类型的所有属性变为必选属性。

举例

type Person = {
  name?: string;
  age?: number;
};

function validatePerson(person: Required<Person>): boolean {
  return person.name !== undefined && person.age !== undefined;
}

const person: Person = {
  name: "John",
};

validatePerson(person); // 类型错误,缺少必选属性 age

6,Exclude

用于从一个联合类型中排除指定的类型,并生成一个新的联合类型

Exclude 类型接受两个参数:原始类型和要排除的类型。它会返回一个新的联合类型,该类型包含了原始类型中除去指定类型的所有其他类型。

例如,Exclude<T, U> 表示从 T 类型中排除 U 类型的属性。

定义:

type Exclude<T, U> = T extends U ? never : T;

可以看到,Exclude 类型使用了条件类型,通过判断原始类型 T 是否可以赋值给要排除的类型 U,来决定是否将 T 包含在结果类型中。如果 T 可以赋值给 U,则返回 never 类型,否则返回 T 类型本身。

因为 T 是联合类型,所以会逐个判断是否可以赋值给类型 U

示例

type Colors = 'red' | 'green' | 'blue'; // 字符串字面量类型
type ExcludedColors = Exclude<Colors, 'green'>; // 'red'| 'blue';

const color: ExcludedColors = 'red';

console.log(color);  // 输出:'red'

Omit 类型相比,Exclude 类型的作用更加通用,它可以用于排除任意类型,而不仅限于对象属性。
Omit 类型则专门用于排除对象的属性。

示例2:排除对象的属性

其实是将对象属性通过 keyof 转换为联合类型。

type Person = {
  name: string;
  age: number;
  gender: string;
};

type ExcludeGender = Exclude<keyof Person, "gender">; // 类型为 "name" | "age"

function getPersonInfo(person: Person, key: ExcludeGender): string {
  return person[key].toString();
}

const person: Person = {
  name: "John",
  age: 30,
  gender: "male",
};

const name = getPersonInfo(person, "name"); // 类型为 string
const age = getPersonInfo(person, "age");   // 类型为 number

7,Pick

Pick:用于从一个类型中选取指定的属性。例如,Pick<T, K> 表示从 T 类型中选取类型 K 的属性。

举例

type Person = {
  name: string;
  age: number;
  gender: string;
};

type PersonInfo = Pick<Person, "name" | "age">; // 类型为 { name: string; age: number; }

function getPersonInfo(person: Person, info: keyof PersonInfo): string {
  return person[info].toString();
}

const person: Person = {
  name: "John",
  age: 30,
  gender: "male",
};

const name = getPersonInfo(person, "name"); // 类型为 string
const age = getPersonInfo(person, "age");   // 类型为 number

8,Omit

用于从一个类型中排除指定的属性,并生成一个新的类型。

Omit 类型接受两个参数:原始类型和要排除的属性。它会返回一个新的类型,该类型包含了原始类型的所有属性,但排除了指定的属性。

定义:

Copy code
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

可以看到,Omit 类型内部使用了 Pick 和 Exclude 类型。

与 Exclude 类型相比,Omit 类型的作用更为具体,它专门用于从一个类型(接口或对象)中排除指定的属性

示例

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

type PersonWithoutEmail = Omit<Person, 'email'>; // { name: string, age: number }

const person: PersonWithoutEmail = {
  name: 'John',
  age: 25,
};

console.log(person);  // 输出:{ name: 'John', age: 25 }

这些泛型约束类型可以灵活地组合使用,从而构建出更加复杂的类型约束。需要注意的是,使用泛型约束时要避免过度约束,否则可能会影响代码的可扩展性和可维护性。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

下雪天的夏风

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

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

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

打赏作者

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

抵扣说明:

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

余额充值