如何理解 TS 中的泛型

114 阅读4分钟

官方文档中这样描述:

在像 C# 和 Java 这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。

恒等函数:返回一个任何传进内容的函数

如果想要恒等函数一个具体的类型可能会这么写:

 function fn(arg:number):number{
   return arg;
 }
 // 或者
 function fn(arg:string):string{
   return arg;
 }
 // 又或者
 function fn(arg:any):any{
   return arg;
 }

尽管使用 any类型可以让我们接受任何类型的 arg 参数,但也让我们丢失了函数返回时的类型信息。

基本使用

使用泛型来定义上述函数:

  • 泛型的语法是 <> 里写类型参数(类型变量),一般可以用 T 来表示或者其他任何表示。T 相当于一个占位符或者变量,在使用时把定义的类型像参数传入。
  • 泛型可以在定义函数、接口或类时不预先指定类型,而是使用时指定类型。
function fn<T>(arg:T):T{
  return arg;
}

// 调用方式
 fn(10); // 类型参数推断, 编译器通过传入的变量来判断属于哪种类型
 fn<string>("abc"); // 显示的定义传入的变量类型为string

指定多个参数:

 // 指定多个类型
 function fn2<T,K>(a:T,b:K):T{
  return a;
 }

fn2(12,"ab")

泛型约束

泛型在成员之间提供有意义的约束,这些成员可以是:函数参数、函数返回值、类的属性、类的方法等。

如下:在函数内部使用函数变量时由于不清楚函数的类型,所以不能随便操作它的属性和方法

  function getLength<T>(arg:T){
    console.log(arg.length)  // 报错
    return arg
  }

11.png

为此 ,我们需要定义一个接口,用来描述约束

interface lengthFn{
   length:number;
 }

 function getLength<T extends lengthFn>(arg:T){
  console.log(arg.length)
  return arg
}
// 只要有 length 属性都可以调用
getLength([1,2,3])
getLength({name:11,length:2})

泛型接口

定义接口的时候指定泛型。

interface commonFn<T> {
  (arg:T):T;
 }

 let myFun:commonFn<string> = function (aa){
  return aa;
 }

 myFun("abc");

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

let numberFun:commonFn<number> = identity;
numberFun(100);

interface commonFn<T = number> { // 给泛型加默认参数
  (arg:T):T;
 }

泛型类

在类名后面,使用尖括号中 <>包裹住类型参数列表:确保类中所有属性都使用了相同的类型。

class TestNumber<T>{
  value:T;
  add:(a:T,b:T)=>T
  
}

let myTestNumber = new TestNumber<number>();
myTestNumber.value = 1;
myTestNumber.add=(a:number,b:number)=>{
  return a + b;
};

泛型工具类型

  1. typeof 类型推断
// typeof 进行类型推断
const p1= {name:"tom",age:12};
type people  = typeof p1;
  1. keyof 获取对象中所有的 key 值
// keyof  

interface Person {
  name: string;
  age: number;
  gender: "male" | "female";
}

type PersonKey = keyof Person;  //type PersonKey = 'name'|'age'|'gender';

function getValueByKey(p: Person, key: PersonKey) {
  return p[key];
}
let val = getValueByKey({ name: "tom", age: 18, gender: "male" }, "name");
console.log(val);
  1. in
type Keys = "a" | "b" | "c"
type Obj =  {
  [p in Keys]: any
} // -> { a: any, b: any, c: any }
  1. infer
// infer

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

内置工具类型

pick

从一个类型中摘取某一部分属性

// eg:公共的类型
 interface userInfo{
   name:string;
   password:string;
   id:number;
   age:number;
 }
// 只想摘取一部分属性作为类型约束
 type loginDataType = Pick<userInfo,'name'|'password'>

 const loginData:loginDataType ={
   name:"tom",
   password:"123"
 }

omit

从一个类型剔除某些属性

// eg:公共的类型
 interface userInfo{
   name:string;
   password:string;
   id:number;
   age:number;
 }

 // 剔除一部分属性, 从哪个对象中剔除,剔除哪些属性
 type resDataType =Omit<userInfo,'password'>
 const resData:resDataType={
  name:"jack",
  id:1,
  age:22
}

interface 和type 区别

type 类型别名

给你的类型起一个新名字,可以定义原始值、联合类型、元组、对象、函数以及其他任务你需要手写的类型。

// type 定义函数
type getAddType =(a:number,b:number)=>number;
const add:getAddType =(a:number,b:number)=>{
  return a + b;
}

// 定义对象
type PersonA = {
  name:string;
  age:number;
}

// 报错 type 不能重复定义
type PersonA = {
  gender:string;
}

// 基本类型
type Name = string;
type Age = number;

 // 联合类型
type Item = Name | Age;
const aaa:Item = 1;

// 交叉类型 继承
type StudentA  = PersonA & { class:string };
// 元组类型
type StudentAndTeacherList = [StudentA, {gender:string}]

// 类型别名扩展接口
type StudentPro = PersonA & {
   class:string;
}

interface 接口

interface 只能定义对象和函数。

// interface 定义对象
interface Person {
  name:string;
  age:number;
 }

// 可重复声明,会合并类型
interface Person{
  gender:string;
  sayHello?:void;            
 }

let p:Person = { name:"John", age:20, gender:"女" };

// interface 定义函数
interface getAddType{
   (a:number,b:number):number;
 }
const add:getAddType =(a:number,b:number)=>{
  return a + b;
}

// 继承
interface Student extends Person {
    class:string
}
 
let ss:Student = { name:"John", age:20, gender:"女",class:'1'  }

// 接口扩展类型别名
interface Sister extends Item {
    name: string;
}

区别:

  1. type 可以定义原始值、联合类型、元组、对象、函数以及其他任务你需要手写的类型。

    interface 只能定义函数和方法。

  2. type 不能重复定义

    interface 可重复定义,会合并声明

  3. type 和 interface 都允许继承,可以混合继承,也就是说 interface  可以扩展 typetype也可以扩展 interface。接口的扩展是继承( extends  )。类型别名的扩展就是交叉类型(通过 &实现)。

小结

  1. 泛型可以定义重用组件,在使用时传入不同类型来使用组件。
  2. 泛型是指在定义函数、接口、类时可以不预先指定类型,而是使用时确定类型。
  3. 泛型一般用<T> 来表示,T 相当于一个占位符或者变量,当使用时才将类型当入参数传入。
  4. 泛型约束: 在成员之间提供了有意义的约束,这些成员可以是 函数参数,函数返回值,类的属性,类型的方法等。