一文搞懂TS中的泛型

一文搞懂TS中的泛型
1. 什么是泛型

为了便于大家更好地理解f泛型,我们先来举个例子,在这个例子中,我们将一步步揭示泛型的作用。首先我们来定义一个通用的 identity 函数,该函数接收一个参数并直接返回它:

function identity (value) {
  return value;
}
console.log(identity(24)) // 24

现在,我们将 identity 函数做适当的调整,以支持 TypeScript 的 number 类型的参数:

function identity(value: number):number {
  return value;
}
console.log(identity(24)) // 24

这里 identity 的问题是我们将 number 类型分配给参数和返回类型,使该函数仅可用于该原始类型number。但该函数并不是可扩展或通用的。很明显这并不是我们所希望的。所以当我们想要定义一个变量不确定类型的时候我们该怎么做呢,其实有两种方式:

  1. 使用any

    我们失去了定义应该返回哪种类型的能力,并且在这个过程中使编译器失去了类型保护的作用

  2. 使用泛型

    不预先指定具体的类型,而是在使用的时候在指定类型限制

// 其中T代表一个类型变量,在泛型中通常作为第一个变量名称,除此之外还有一些常见的变量名称
// K(Key):表示对象中的键类型;
// V(Value):表示对象中的值类型;
// E(Element):表示元素类型。
function identity <T>(value: T) : T {
  return value;
}
console.log(identity<number>(24)) // 24

// 除了为类型变量显式设定值之外,一种更常见的做法是使编译器自动选择这些类型,从而使代码更简洁。我们可以完全省略尖括号,比如:
console.log(identity(24)) // 24
2.泛型的用法
  • 函数中使用

    使用方式类似于函数传参,传什么数据类型,T就表示什么数据类型,T也可以换成任意字符串

    function identity <T> (arg:T):T{
      console.log(arg);
      return arg;
    }
    identity<number>(24);// 返回值是number类型的 24
    identity<string | boolean>('identity')//返回值是string类型的 identity
    identity<string | boolean>(false);//返回值是布尔类型的 false
    
  • 接口中使用

  // 注意,这里写法是定义的方法哦
  interface Person {
    <T,Y>(name:T,age:Y):Y
  }
  const fn:Person = function <T, Y>(name: T, age:Y):Y {
    console.log(name, age)
    return age;
  }
  fn('ymx',24);//编译器会自动识别传入的参数,将传入的参数的类型认为是泛型指定的类型
  • 类中使用
class Animal<T> {
 name:T;
 constructor(name: T){
  this.name = name;
 }
 action<T>(say:T) {
   console.log(say)
 }
}
const dog = new Animal('蛋黄');
dog.action('你好,小蛋黄')

3. 泛型约束

​ 有时我们可能希望限制每个类型变量接受的类型数量,这就是泛型约束的作用。下面我们来举几个例子,介绍一下如何使用泛型约束。

  • 确保属性存在 : 有时候,我们希望类型变量对应的类型上存在某些属性。这时,除非我们显式地将特定属性定义为类型变量,否则编译器不会知道它们的存在。

    function identity<T>(arg: T): T {
      console.log(arg.length); // Error
      return arg;
    }
    

    在这种情况下,编译器将不会知道 T 确实含有 length 属性,尤其是在可以将任何类型赋给类型变量 T 的情况下(比如number,number实际上是没有length属性的)。我们需要做的就是让类型变量 extends 一个含有我们所需属性的接口,比如这样:

    interface Length {
      length: number;
    }
    
    function identity<T extends Length>(arg: T): T {
      console.log(arg.length); // 可以获取length属性
      return arg;
    }
    

    其中 T extends Length 就相当于告诉编辑器,我们支持已经实现 Length 接口的任何类型。之后,当我们使用不含有 length 属性的对象作为参数调用 identity 函数时,TypeScript 会提示相关的错误信息:

    identity(24); // 类型“number”的参数不能赋给类型“Length”的参数。
    
  • 检查对象上的健是否存在

// 限制输入的属性名包含在 keyof 返回的联合类型中。具体的使用方式如下
function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

在以上的 getValue 函数中,我们通过 K extends keyof T 确保参数 key 一定是对象中含有的键,这样就不会发生运行时错误。

下一步我们来看看如何使用这个getValue函数:

enum AnnotateType {
  POINT = "POINT",
  SQUARE = "SQUARE",
  LATTICE = "LATTICE",
}

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

const info = {
   name: "ymx",
   annotateType: AnnotateType.POINT
}
 
const point: AnnotateType = getValue(info, 'annotateType'); // OK
const lattice: string = getValue(info, '_LATTICE_'); // Error

4. 泛型工具类型
  • Partial: 的作用就是将某个类型中的属性全部变为可选项

    interface Person {
      name:string;
      age:number;
    };
    const Person1 = Partial<Person>;
    interface Person1 {
      name?:string;
      age?:number;
    }
    // Partial实现原理
    type Partial<T> = {
        [P in keyof T]?: T[P];
    };
    
  • Record: 的作用是将K中所有的属性转换为T类型

interface PersonInfo {
  name: string
}
type Option = 'device'|'edge';
const x: Record<Option, PersonInfo> = {
  device: { name: "xxx" },
  edge: { name: "aaa" },
};
// Record实现原理
type Record<K extends keyof any, T> = {
    [P in K]: T;
};
  • Pick: 作用是将某个类型中的子属性挑出来,变成包含这个类型部分属性的子类型
interface Todo {
  title:string,
  desc:string,
  time:string
}
type TodoPreview = Pick<Todo, 'title'|'time'>;
const todo: TodoPreview ={
  title:'吃饭',
  time:'明天'
}

// Pick实现原理
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};
  • PickWithPartial: 我们想让Pick返回的类型可选,参照Partial得出我们新造出来的新工具泛型

    const someStudentBodyInfo:PickWithPartial<Todo, 'title'|'time'> = {
    	time: '后天'
    }
    //PickWithPartial实现原理
    type PickWithPartial<T, K extends keyof T> = {
        [P in K]?: T[P];
    };
    
  • Exclude: 的作用是将某个类型中属于另一个类型的属性移除掉

    type letter = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
    const letters:letter ='b';
    // Exclude实现原理
    type Exclude<T, U> = T extends U ? never : T;
    
  • Omit : 跟Pick相反,把选出的排除掉

interface User {
  id: number;
  name: string;
  age: number;
  sex: 0 | 1;
  tel: number;
}

type EditUser = Omit<User, "id">; // 就是在 User 的基础上,去掉 id 属性

// Omit实现原理
type Omit<T, K extends keyof any>{
	[P in Exclude<keyof T, K>]: T[P];
}
  • Extract: 跟Exclude相反,从从一个联合类型中取出属于另一个联合类型的子集
type name = Exclude<'小明' | '小张', '小张' | '小王'>
// 等同
type name = '小张'
// Extract实现原理
type Extract<T, U> = T extends U ? T : never;
  • Readonly: 所有属性只读
let arr: Readonly<number[]> = [1] 
arr = [1] // 此时arr=[1]
arr.push(2) // 类型“readonly number[]”上不存在属性“push”。

// Readonly实现原理
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
}
  • Required: 与Partial相反,把属性全部转化成必填类型

    interface PartProps {
        a?: string
        b?: string
    }
    // 因为Required,所以要求a, b属性必选
    const a:Required<PartProps> = {
       a: 'a',
       b: 'b'
    }
    
    // Required实现原理
    type Required<T> = {
        [P in keyof T]-?: T[P];
    };
    
  • NonNullable: 排除null/undefined类型

type studentName = NonNullable<string | null | undefined> // string
// NonNullable实现原理
type NonNullable<T> = T extends null | undefined ? never : T;
  • ReturnType: 返回函数的返回结果类型

    type T0 = ReturnType<() => string>; // string
    type T1 = ReturnType<(s: string) => void>; // void
    type T2 = ReturnType<<T>() => T>; // {}
    type T3 = ReturnType<<T extends U, U extends number[]>() => T>; // number[]
    type T4 = ReturnType<any>; // any
    type T5 = ReturnType<never>; // any
    type T6 = ReturnType<string>; // Error
    type T7 = ReturnType<Function>; // Error
    
    // ReturnType实现原理
    type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
    
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值