一、泛型介绍

在定义函数的时候,有时候我们可能并不能确定要传入或者返回是什么类型的参数/结果,
比如,实现一个函数,返回任何他传入的值。这个时候我们可能会想到如下的解决方法:

  1. function identity(arg: any): any {
  2. return arg;
  3. }

使用any类型确实可以接受任何类型的参数,但是这样会导致一些问题:传入的类型可能与返回的类型不一致,因此我们需要一种方法使得返回值的类型与传入参数的类型时相同的。

  1. function identity<T>(arg: T): T {
  2. return arg;
  3. }
  4. identity<string>('leah')
  5. identity<number>(123)

给identity添加了类型变量T。 T帮助我们捕获用户传入的类型(比如:string number),之后我们就可以使用这个类型。 之后我们再次使用了 T当做返回值类型。现在我们可以知道参数类型与返回值类型是相同的了。 这允许我们跟踪函数里使用的类型的信息。

其中 T 代表 Type,在定义泛型时通常用作第一个类型变量名称。但实际上 T 可以用任何有效名称代替。除了 T 之外,以下是常见泛型变量代表的意思:

  • K(Key):表示对象中的键类型;
  • V(Value):表示对象中的值类型;
  • E(Element):表示元素类型。

    1.使用泛型方法一

    第一种方法就是向我们上面那样传入所有的参数,包含类型参数: ```typescript function createArray1(length: number, value: T): T[]{ let result: T[] = []; for(let i = 0; i < length; i++){
    1. result[i] = value;
    } return result; } let result = createArray1(3, ‘x’);
  1. 明确的指定了 T string类型,并做为一个参数传给函数,使用了<>括起来而不是()
  2. <a name="UuBhZ"></a>
  3. ## 2.使用泛型方法二
  4. _类型推论_ -- 即编译器会根据传入的参数自动地帮助我们确定T的类型:
  5. ```typescript
  6. let result2 = createArray1(3, 'x');

编译器可以查看myString的值,然后把T设置为它的类型。 类型推论帮助我们保持代码精简和高可读性。

二、泛型约束

我们有时候想操作某类型的一组值,并且我们知道这组值具有什么样的属性。比如我们想访问参数的length属性,但是编译器就会认为参数是任意类型的,并不能保证每种类型都有length属性,所以就会报错。

  1. function loggingIdentity1<T>(arg: T): T{
  2. console.log(arg.length) //类型“T”上不存在属性“length”
  3. return arg
  4. }

既然编译器不能保证我们传入的参数有 length 属性,那我们就自己保证我们传入的这个参数有这个属性就不会报错了。 只要传入的类型有这个length属性,我们就允许,就是说至少包含这一属性。 为此,我们需要列出对于T的约束要求。
为此,我们定义一个接口来描述约束条件。 创建一个包含 .length属性的接口,使用这个接口和extends关键字来实现约束:

  1. interface Lengthwise{
  2. length: number;
  3. }
  4. function loggingIdentity4<T extends Lengthwise>(arg: T): T{
  5. console.log(arg.length)
  6. return arg
  7. }

注意📢:我们不能直接将loggingIdentity4 的参数限定为 Lengthwise 类型,因为这样会有类型丢失的风险。

三、泛型变量

我们有时候想操作某类型的一组值,并且我们知道这组值具有什么样的属性。 在 loggingIdentity例子中,我们想访问arg的length属性,但是编译器并不能证明每种类型都有length属性,所以就报错了。

  1. function loggingIdentity1<T>(arg: T): T{
  2. console.log(arg.length) //类型“T”上不存在属性“length”
  3. return arg
  4. }

现在假设我们想操作T类型的数组而不直接是T。由于我们操作的是数组,所以.length属性是应该存在的。 我们可以像创建其它数组一样创建这个数组:

  1. function loggingIdentity<T>(arg: T[]): T[] {
  2. console.log(arg.length);
  3. return arg;
  4. }

泛型函数loggingIdentity,接收类型参数T和参数arg,它是个元素类型是T的数组,并返回元素类型是T的数组。 如果我们传入数字数组,将返回一个数字数组,因为此时 T的的类型为number。 这可以让我们把泛型变量T当做类型的一部分使用,而不是整个类型,增加了灵活性。


四、泛型类型别名

泛型类型别名可以表达更复杂的类型。

  1. type Cart<T> = {list: T[]} | T[] // 接口是无法实现这种的 | ,能用接口实现的就不用type
  2. let c1:Cart<string> = {
  3. list:['1']
  4. }
  5. let c2:Cart<number> = [2]

五、泛型类

给 类传个泛型 T ,就可以在属性里使用 T, 泛型类指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型。

  1. class MyArray<T>{
  2. private list:T[] = []
  3. num: T[] = []
  4. add(value: T){
  5. this.list.push(value)
  6. }
  7. getMax():T{
  8. return this.list[0]
  9. }
  10. }
  11. let myArray = new MyArray<number>()
  12. myArray.list = [1, 2, 3] // Property 'list' is private and only accessible within class 'MyArray<T>'.ts(2341)
  13. myArray.num = [3, 4, 5]
  14. myArray.add = function (v){
  15. }

list 是个类的静态属性,只能被类本身调用,他的实例不能调用。

六、泛型函数

1.使用普通形式定义

  1. function addArray<T>(length: number, value: T):T[] {
  2. let result: T[] = [];
  3. for(let i = 0; i < length; i++){
  4. result[i] = value;
  5. }
  6. return result;
  7. }

2.类型别名定义

  1. type AddArray = <T>(length: number, value: T) => T[]
  2. const addArray2: AddArray = function(length, value){
  3. let result = [];
  4. for(let i = 0; i < length; i++){
  5. result[i] = value;
  6. }
  7. return result;
  8. }
  9. addArray2(2, 'hh')
  10. addArray2(2, 200)

3.接口形式定义

  1. const addArray3: AddArray3 = function(length, value){
  2. let result = [];
  3. for(let i = 0; i < length; i++){
  4. result[i] = value;
  5. }
  6. return result;
  7. }
  8. addArray3(2, 'hh')
  9. addArray3(2, 200)

七、泛型工具类型

https://juejin.cn/post/7018805943710253086#heading-75