当前位置: 首页 > article >正文

一文搞懂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):T
  }
  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;
    

http://www.kler.cn/news/17825.html

相关文章:

  • 【Unity编辑器】拓展Inspector视图
  • 【Python百日进阶-Web开发-Feffery】Day617- 趣味dash_17:微型系统--引入flask-login
  • vue+node.js高校青年志愿者管理系统
  • AI来势汹汹,这份「生存计划」请查收!
  • 搭建vue3+vite工程
  • HTTPS协议介绍
  • 4.4——多重继承
  • 树形结构的三级分类如何实现?
  • 如何训练自己的大型语言模型
  • powershell定义文本,用户交互,正则表达式
  • 宝塔面板搭建自己的网站,并发布公网远程访问
  • 企业服务管理(ESM)工具
  • MySQL中的Join 的算法(NLJ、BNL、BKA)
  • XXLJOB
  • 知识变现海哥|这3种课程定价最容易爆单
  • asp.net+sqlserver企业财务管理系统
  • 【Mybatis】增删改查
  • 北斗导航 | 基于差分法的周跳探测(matlab 代码)
  • android pdf框架
  • unity航点寻径
  • js中对象和数组的都是如何解构的
  • 二挡起步——pythonweb开发Django框架,前端原生+Django后端框架+python网络抓包(代替数据库数据)(附带小案例)
  • Java学习之Swing图形界面
  • 关于面试官问Qt Connect的链接方式和类型问题
  • jQuery -- 常用API(下)
  • 《Netty》从零开始学netty源码(五十二)之PoolThreadCache
  • SpringBoot整合FreeMarker
  • 使用zabbix监控Windows指定服务| zabbix Windows service filter
  • 【网络】4万字细品TCP协议
  • yolov8 OpenCV DNN 部署 推理报错