TypeScript(四)泛型编程

TypeScript

软件工程的主要目的是构建不仅仅明确和一致的API,还要让你的代码具有很强的可重用性:
比如我们可以通过函数来封装一些API,通过传入不同的函数参数,让函数帮助我们完成不同的操作;
但是对于参数的类型是否也可以参数化呢?

什么是类型的参数化?
我们来提一个需求:封装一个函数,传入一个参数,并且返回这个参数;
如果我们是TypeScript的思维方式,要考虑这个参数和返回值的类型需要一致:

function foo(arg: number):number {
	return arg
}

上面的代码虽然实现了,但是不适用于其他类型,比如string、boolean、Person等类型:
我么可以使用any来解决这个问题

function foo(arg: any):any {
	return arg
}

虽然any是可以的,但是定义为any的时候,我们其实已经丢失了类型信息:
比如我们传入的是一个number,那么我们希望返回的可不是any类型,而是number类型;
所以,我们需要在函数中可以捕获到参数的类型是number,并且同时使用它来作为返回值的类型;

我们需要在这里使用一种特性的变量 - 类型变量(type variable),它作用于类型,而不是值:

function foo<Type>(arg: Type): Type{
	return arg
}

这里我们可以使用两种方式来调用它:
方式一:通过 <类型> 的方式将类型传递给函数;
方式二:通过类型推导(type argument inference),自动推到出我们传入变量的类型:

在这里会推导出它们是 字面量类型的,因为字面量类型对于我们的函数也是适用的

foo<string>('abc')
foo<number>(123)

foo('bcd')
foo(234)

泛型的基本补充

当然我们也可以传入多个类型

function foo<T, E>(al: T, a2: E) {

}

平时在开发中我们可能会看到一些常用的名称:
T:Type的缩写,类型
K、V:key和value的缩写,键值对
E:Element的缩写,元素
O:Object的缩写,对象

泛型接口

在定义接口的时候我们也可以使用泛型

interface IFoo<T> {
	initialValue: T,
	valueList: T[],
	handleValue: (value: T) => void
}

fonst foo: IFoo<number> = {
	initialValue: 0,
	valueList: [0, 2, 3],
	handleValue: function(value: number) {
		console.log(value)
	}
}

泛型类

我们也可以编写一个泛型类:

class Point<T, K> {
	x: T;
	y: K;
	constructor(x: T, y: K) {
		this.x = x;
		this.y = y;
	}
}

const p1 = new Point(10, 20);
const p2 = new Point<string, string>('123', '234');

console.log(p1.x);
console.log(p2.x);

泛型约束(Generic Constraints)

有时候我们希望传入的类型有某些共性,但是这些共性可能不是在同一种类型中:
比如string和array都是有length的,或者某些对象也是会有length属性的;
那么只要是拥有length的属性都可以作为我们的参数类型,那么应该如何操作呢?

interface ILength {
	length: number;
}
//T相当于是一个变量 用于记录本次调用的类型 所以在整个函数的执行周期中 一直保留着参数类型
function getInfo<T extends ILength>(args: T): T {
	return args;
}

const info1 = getInfo('aaaa');
const info2 = getInfo(['aaa', 'bbb', 'ccc']);
const info3 = getInfo({ length: 100 });

这里表示是传入的类型必须有这个属性,也可以有其他属性,但是必须至少有这个成员。

在泛型约束中使用类型参数(Using Type Parameters in Generic Constraints)

你可以声明一个类型参数,这个类型参数被其他类型参数约束;
举个栗子:我们希望获取一个对象给定属性名的值
我们需要确保我们不会获取 obj 上不存在的属性;
所以我们在两个类型之间建立一个约束;

//传入的key的类型 obj当中key的其中之一

interface IPerson{
	name: string;
	age: number;
}
type IPersonKeys = keyof IPerson; //相当于 "name" | "age"  key的联合类型

// function getObjectProperty<O, K extends keyof 'name' | 'age' | 'height'>(obj: O, key: K) {
// 	return obj[key];
// }
function getObjectProperty<O, K extends keyof O>(obj: O, key: K) {
	return obj[key];
}

const info = {
	name: 'kobe',
	age: 18,
	height: 1.88,
};

const name = getObjectProperty(info, 'name');

映射类型(Mapped Types)

有的时候,一个类型需要基于另外一个类型,但是你又不想拷贝一份,这个时候可以考虑使用映射类型。
大部分内置的工具都是通过映射类型来实现的;
大多数类型体操的题目也是通过映射类型完成的;

映射类型建立在索引签名的语法上:
映射类型,就是使用了 PropertyKeys 联合类型的泛型;
其中 PropertyKeys 多是通过 keyof 创建,然后循环遍历键名创建一个类型;

ts提供了映射类型: 函数
映射类型不能使用interface定义

type MapPerson<Type> = {
	// 索引类型依次进行使用
	[Property in keyof Type]: Type[Property];
};

interface IPerson {
	name: string;
	age: number;
}
type NewPerson = MapPerson<IPerson>;

映射修饰符(Mapping Modifiers)

在使用映射类型时,有两个额外的修饰符可能会用到:
一个是 readonly,用于设置属性只读;
一个是 ? ,用于设置属性可选;

你可以通过前缀 - 或者 + 删除或者添加这些修饰符,如果没有写前缀,相当于使用了 + 前缀。

type MapIPerson<Type> = {
	readonly [Property in keyof Type]?: Type[Property];
};

interface IPerson {
	name: string;
	age: number;
	height: number;
	address: string;
}

type IPersonOptional = MapIPerson<IPerson>;
const p: IPersonOptional = {};

在这里插入图片描述

type MapIPerson<Type> = {
	-readonly [Property in keyof Type]-?: Type[Property];
};

interface IPerson {
	name: string;
	age?: number;
	readonly height: number;
	address: string;
}

type IPersonOptional = MapIPerson<IPerson>;

内置工具与类型体操

类型系统其实在很多语言里面都是有的,比如Java、Swift、C++等等,但是相对来说TypeScript的类型非常灵活:
这是因为TypeScript的目的是为JavaScript添加一套类型校验系统,因为JavaScript本身的灵活性,也让TypeScript类型系统
不得不增加更附加的功能以适配JavaScript的灵活性;
所以TypeScript是一种可以支持类型编程的类型系统;
这种类型编程系统为TypeScript增加了很大的灵活度,同时也增加了它的难度:
如果你不仅仅在开发业务的时候为自己的JavaScript代码增加上类型约束,那么基本不需要太多的类型编程能力;
但是如果你在开发一些框架、库,或者通用性的工具,为了考虑各种适配的情况,就需要使用类型编程;
TypeScript本身为我们提供了类型工具,帮助我们辅助进行类型转换(前面有用过关于this的类型工具)。

类型体操的题
类型体操的一些题解

条件类型(Conditional Types)

很多时候,日常开发中我们需要基于输入的值来决定输出的值,同样我们也需要基于输入的值的类型来决定输出的值的类型。
条件类型(Conditional types)就是用来帮助我们描述输入类型和输出类型之间的关系。
条件类型的写法有点类似于 JavaScript 中的条件表达式(condition ? trueExpression : falseExpression ):
SomeType extends OtherType ? TrueType : FalseType;

function sum<T extends number | string>(num1: T, num2: T): T extends number ? number : string;
function sum(num1, num2) {
	return num1 + num2;
}
const res = sum(20, 30);
const res2 = sum('abc', 'cba');

在条件类型中推断(inter)

在条件类型中推断(Inferring Within Conditional Types)
条件类型提供了 infer 关键词,可以从正在比较的类型中推断类型,然后在 true 分支里引用该推断结果;
比如我们现在有一个数组类型,想要获取到一个函数的参数类型和返回值类型:

type CalcFnType = (num1: number, num2: number) => number;

function foo() {
	return 'abc';
}
//同届类型体操的题目: MyReturnType
//推断返回值类型
type MyReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R
	? R
	: false;

//推断参数类型
type MyParameterType<T extends (...args: any[]) => any> = T extends (...args: infer A) => any
	? A
	: never;

//获取一个函数的返回值类型
type CalcReturnType = MyReturnType<CalcFnType>;
type FooReturnType = MyReturnType<typeof foo>;
type FooReturnType1 = ReturnType<CalcFnType>;
type CalcParameterType = MyParameterType<CalcFnType>;

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

分发条件类型(Distributive Conditional Types)

在泛型中使用条件类型的时候 如果传入一个联合类型 就会变成分发的

// type toArray<T> = T[]; //(string | number)[]
type toArray<T> = T extends any ? T[] : never; //number[] | string[]

type NumArray = toArray<number>;

//number[] | string[] 而不是(number | string)[]
type NumAndStrArray = toArray<number | string>;

如果我们在 ToArray 传入一个联合类型,这个条件类型会被应用到联合类型的每个成员:
当传入string | number时,会遍历联合类型中的每一个成员;
相当于ToArray | ToArray;
所以最后的结果是:string[] | number[];

Partial

用于构造一个Type下面的所有属性都设置为可选的类型

interface IPerson {
	name: string;
	age: number;
	slogan?: string;
}
type IPersonOption2 = Partial<IPerson>;

在这里插入图片描述
实现myPartial

type MyPartial<T> = {
	[p in keyof T]?: T[p];
};

//IKun类型都是可选的
type IPersonOption1 = MyPartial<IPerson>;

Required

用于构造一个Type下面的所有属性全都设置为必填的类型,这个工具类型跟 Partial 相反。

interface IPerson {
	name: string;
	age: number;
	slogan?: string;
}
type IKunOption1 = Required<IPerson>;

实现MyRequired

type MyRequired<T> = {
	[p in keyof T]-?: T[p];
};
type IKunOption2 = MyRequired<IPerson>;

Readonly

用于构造一个Type下面的所有属性全都设置为只读的类型,意味着这个类型的所有的属性全都不可以重新赋值。

interface IPerson {
	name: string;
	age: number;
	slogan?: string;
}
type MyReadonly<T> = {
	readonly [p in keyof T]: T[p];
};
type IPersonOption1 = Readonly<IPerson>;;

实现MyReadonly

type MyReadonly<T> = {
	readonly [p in keyof T]: T[p];
};
type IPersonOption2 = MyReadonly<IPerson>;

Record<Keys, Type>

用于构造一个对象类型,它所有的key(键)都是Keys类型,它所有的value(值)都是Type类型。

interface IPerson {
	name: string;
	age: number;
	slogan?: string;
}

type t1 = '上海' | '北京' | '洛杉矶';
type IPersons= Record<t1, IPerson>;

const ipersons:IPersons = {
	上海: {
		name: 'xxx',
		age: 10,
	},
	北京: {
		name: 'yyy',
		age: 5,
	},
	洛杉矶: {
		name: 'zzz',
		age: 3,
	},
};

实现MyRecord

//确保keys一定是可以作为key的联合类型 Keys extends keyof any
type keys = keyof IPerson; // name | age | slogan
type Res = keyof any; // =>number | string | symbol

type MyRecord<Keys extends keyof any, T> = {
	[P in Keys]: T;
};

type t1 = '上海' | '北京' | '洛杉矶';
type IPersons = MyRecord<t1, IPerson>;

Pick<Type, Keys>

用于构造一个类型,它是从Type类型里面挑了一些属性Keys

interface IPerson {
	name: string;
	age: number;
	slogan?: string;
}

type IPerson1 = Pick<IPerson, 'name' | 'slogan'>;

实现MyPick

type MyPick<T, K extends keyof T> = {
	[P in K]: T[P];
};

type IPerson2 = MyPick<IPerson, 'name' | 'slogan'>;

Omit<Type, Keys>

用于构造一个类型,它是从Type类型里面过滤了一些属性Keys

interface IPerson {
	name: string;
	age: number;
	slogan?: string;
}
type IPerson1 = Omit<IPerson, 'name' | 'slogan'>;

实现MyOmit

//类型体操
type MyOmit<T, K extends keyof T> = {
	[P in keyof T as P extends K ? never : P]: T[P];
};
type IPerson2 = MyOmit<IPerson, 'name' | 'slogan'>;

```

## Exclude<UnionType, ExcludedMembers>
用于构造一个类型,它是从UnionType联合类型里面排除了所有可以赋给ExcludedMembers的类型。
```typescript
type IPerson = '唱' | '跳' | 'rap';
type IPerson1 = Exclude<IPerson, 'rap'>;
```

实现MyExclude
```typescript
type MyExclude<T, E> = T extends E ? never : T;
type IPerson2 = MyExclude<IPerson, 'rap'>;
```
有了MyExclude 我们可以使用它来实现MyOmit
```typescript
type MyOmit<T, K> = Pick<T, MyExclude<keyof T, K>>
```

## Extract<Type, Union>
用于构造一个类型,它是从Type类型里面提取了所有可以赋给Union的类型。

```typescript
type IPerson = '唱' | '跳' | 'rap';
type IPerson1 = Extract<IPerson, 'rap'>;

```
实现MyExtract
```typescript
type MyExtract<T, E> = T extends E ? T : never;
type IPerson2 = MyExtract<IPerson, 'rap'>;
```

## NonNullable<Type>
用于构造一个类型,这个类型从Type中排除了所有的null、undefined的类型。

```typescript
type IPerson = '唱' | '跳' | 'rap' | null | undefined;
type IPerson1 = NonNullable<IPerson>;
```

实现MyNonNullable
```typescript
type MyNonNullable<T> = T extends null | undefined ? never : T;
type IPerson2 = MyNonNullable<IPerson>;
```
## InstanceType<Type>
用于构造一个由所有Type的构造函数的实例类型组成的类型。

```typescript
class Person {}
class Dog {}
const p1: Person = new Person();

//type Person 构造函数具体的类型
//InstanceType 构造函数创建出来的实例对象的类型
type MyPerson = InstanceType<typeof Person>;
const p2: MyPerson = new Person();
```
此时p1和p2都是Person类型 可能会觉得instanceType没有用
但是我们可以看下面的情况
```typescript
function factory<T extends new (...args: any[]) => any>(ctor: T): T {
	return new ctor();
}

const p3 = factory(Person);
const d = factory(Dog);
```
![在这里插入图片描述](https://img-blog.csdnimg.cn/b2749c9547e14104850da1211e223fab.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/4a074c44238d4e2983457089fb93dc41.png)
此时返回的类型不是我们期望的
```typescript

function factory<T extends new (...args: any[]) => any>(ctor: T): InstanceType<T> {
 	return new ctor();
}
const p3 = factory(Person);
const d = factory(Dog);
```
![在这里插入图片描述](https://img-blog.csdnimg.cn/1b6eb5b7f31b440c8b551c47500bb1b7.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/2acb0e63e8064114ab807a1e6f43b37e.png)

此时就正确了


实现MyInstanceType
```typescript
type MyInstanceType<T extends new (...args: any[]) => any> = T extends new (
	...args: any[]
) => infer R
	? R
	: never;
```
### 回答1: TypeScript中的泛型是指一种能够将类型作为参数进行传递的特性。泛型可以帮助我们编写更加通用的代码,因为我们可以在函数、类和接口中使用它来适应各种不同的数据类型。通过使用泛型,我们可以减少代码的冗余,并提高代码的可读性和可维护性。 在TypeScript中,我们可以使用<>符号来定义泛型类型参数,例如: ```typescript function identity<T>(arg: T): T { return arg; } let output = identity<string>("hello world"); ``` 在上面的代码中,identity函数使用了泛型类型参数T,这样我们就可以将任意类型的值传递给这个函数。在这里,我们将泛型类型参数指定为string,所以函数返回的是一个字符串类型的值。 除了函数,泛型还可以用于类和接口的定义中。在实际编程中,使用泛型可以使代码更加灵活、可扩展和易于维护。 ### 回答2: TypeScript泛型指的是在定义函数、类或接口时允许使用参数化类型的特性。通过使用泛型,我们可以在不指定具体类型的情况下编写可复用的代码。 泛型能够实现类型的参数化,使得代码更加灵活和可复用。通过在尖括号中定义泛型参数,我们可以在函数或类中使用这些参数来表示未知的类型。当我们调用带有泛型类型参数的函数或类时,可以根据实际的类型来替换泛型参数。 泛型在多种情况下都有很好的应用,例如可以用于函数中的参数类型、返回值类型以及变量类型的定义。通过泛型,我们可以在定义时指定输入参数的类型,并在使用时获取输入参数的类型,从而增加代码的灵活性和安全性。 泛型还可以在类中使用,定义类的属性、方法或构造函数的参数的类型。通过泛型,可以实现对不同类型的对象进行操作,提高代码的可复用性。 总之,TypeScript泛型为我们提供了一种参数化类型的方式,使代码更加灵活可复用。通过使用泛型,我们可以在定义函数、类或接口时不指定具体类型,在使用时再动态地传入类型参数,从而更好地应对不同情况和需求。 ### 回答3: TypeScript泛型是一种在编写代码时能够处理多种数据类型的机制。它允许我们在定义变量、函数或类时不指定具体的类型,而是在使用时根据需求进行动态的类型推断和检查。 泛型能够提高代码的重用性和灵活性。它让我们能够编写更通用的代码,以适应不同的数据类型。例如,我们可以定义一个泛型函数来处理任意类型的数组,而不需要为每种类型定义不同的函数。这样一来,我们就可以在需要处理不同类型数据的时候直接调用这个函数,而无需重复编写类似的代码。 除了函数之外,泛型还可以用于类和接口的定义。我们可以定义一个泛型类来表示一种具有通用特性的数据结构,例如链表或栈。在类的内部,我们可以使用泛型类型作为成员变量或方法参数,以便处理不同类型的数据。类似地,我们也可以使用泛型接口来定义一种约束规范,以确保实现类符合要求。 另外,TypeScript还提供了一些高级的泛型技巧,例如泛型约束和泛型扩展。使用泛型约束,我们可以限制泛型类型只能是特定的类型或遵循特定的接口。通过泛型扩展,我们可以在泛型类型上定义更丰富的操作,以便更灵活地处理数据。 总之,TypeScript泛型是一种强大的机制,它可以在我们编写代码时提供更多的灵活性和可重用性。使用泛型,我们可以编写更通用、更安全的代码,以适应不同的数据类型和需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

_聪明勇敢有力气

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

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

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

打赏作者

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

抵扣说明:

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

余额充值