TypeScript学习笔记 | 泛型 - 泛型的应用 - 泛型工具类 - extends - infer用法

TypeScript学习笔记 | 泛型 - 泛型的应用

参考文章:https://juejin.cn/post/6844904184894980104

什么是泛型

泛型指是在定义函数或者类时并不知道类型,在被使用时才知道数据的类型。
泛型先起到一个占位的作用,在使用时接收传递过来的类型。

也可以将泛型类比于参数,参数接收的是变量值,泛型接收的是类型。

类型兼容

研究 TypeScript 的关键问题就是讨论两个类型之间的兼容性,类型之间的关系简单分为三种

  • A 是 B 的子类型
  • A 是 B 的超类型
  • A 和 B 不兼容

子类型与子类不同,仅描述两个类型之间的兼容性,且子类型是包括自身的,就像集合子集也包括自己一样。
作用:保证属性是存在

// 比如 Cat 是  Animal 的子类型,那么类型为 Cat  的变量就可以赋给类型为 Animal  的变量了
// Cat 可以保证有 Animal 上的属性,反之不成立
let b : Animal;
let a : Cat;
b = a ; // 正确
a = b ; // 错误
鸭子类型

鸭子类型是 TypeScript 类型的最大特性:仅关注对象上的属性和方法,而不关注继承关系
鸭子类型:只关心属性和行为是否一样,并不关心是不是具体对应的类型(走得像鸭子声音像鸭子就是鸭子)

Class Duck{ swim } 
Class Dog{ swim,bark }

在 TypeScript 中 Dog 是 Duck 的子类型,因为它满足了 Duck 上的所有方法和属性。

类型运算「&」和「|」

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

  • &:求两个类型的并集,同名属性也对其类型 &
  • |:求两个类型的交集,只保留同名属性且也对其类型

泛型的定义

语法:<类型变量>
在这里插入图片描述
T被称为类型变量(type variable),T作为参数泛化成具体类型的作用叫做类型参数化。

说明
1.泛型和变量一样,可以指定多个。比如<T , U>,中间使用逗号隔开。
2.泛型和变量一样,可以指定默认值。比如< T = string>
3.泛型定义的位置,定义的位置一般在标识符的后面。比如function fn <T> (a:T):T{}

泛型的使用

显示设置:像传递函数一样传递类型给泛型变量。(推荐,更方便阅读)
隐式设置:根据参数类型自动推断类型。(更简洁)

// 显示设置
function identity <T, U>(value: T, message: U) : T {
  console.log(message);
  return value;
}

console.log(identity<number, string>(68, "Semlinker"));

//隐式设置
function identity <T, U>(value: T, message: U) : T {
  console.log(message);
  return value;
}

console.log(identity(68, "Semlinker"));

在这里插入图片描述

泛型接口与泛型类

泛型接口案例

//泛型接口的定义
interface Identities<V, M> {
  value: V,
  message: M
}

//泛型接口的使用
function identity<T, U> (value: T, message: U): Identities<T, U> {
  console.log(value + ": " + typeof (value));
  console.log(message + ": " + typeof (message));
  let identities: Identities<T, U> = {
    value,
    message
  };
  return identities;
}

console.log(identity(68, "Semlinker"));

泛型类案例

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

const p1 = new Point('1.33.2', '2.22.3', '4.22.1')
const p2 = new Point<string>('1.33.2', '2.22.3', '4.22.1')
const p3: Point<string> = new Point('1.33.2', '2.22.3', '4.22.1')

extends用法

  • 接口继承:类似于ES6的Class语法中类的继承。
  • 条件判断:类似于JS的三元表达式

接口继承的用法

interface T1 {
    name: string
  }
  
  interface T2 {
    sex: number
  }
  
  // 多重继承,逗号隔开
  // T3 接口继承于T1,T2接口,T3是T1和T2的子接口,T3接口的要求更多。
  interface T3 extends T1,T2 {
    age: number
  }
  
  // 合法
  const t3: T3 = {
    name: 'xiaoming',
    sex: 1,
    age: 18
  }
泛型用法
// 普通用法
type A1 = 'x' extends 'x' ? string : number; // string
type A2 = 'x' | 'y' extends 'x' ? string : number; // number

// 泛型用法  
type P<T> = T extends 'x' ? string : number;
type A3 = P<'x' | 'y'> // ?

感觉上A2和A3是一样的,但其实A3的类型是 string | number
原因:如果extends前面的参数是一个泛型类型,当传入该参数的是联合类型,则使用分配律计算最终的结果。也就是将联合类型分别拆开带入泛型,最后将结果再联合起来。

特殊的never
never被认为是没有联合项的联合类型,所以还是满足分配律,然而因为没有联合项可以分配,所以P<T>的表达式根本没有执行,所以A2的定义也就类似于永远没有返回的函数一样,是never类型的。

  // never是所有类型的子类型
  type A1 = never extends 'x' ? string : number; // string
  type P<T> = T extends 'x' ? string : number;
  type A2 = P<never> // never

阻止条件判断中的分配律
如果我们希望将联合类型看成一个整体,不适用分配律,那么在条件判断类型的定义中,将泛型参数使用[]括起来。

下面的例子,传入参数T的类型将被当作一个整体。

  type P<T> = [T] extends ['x'] ? string : number;
  type A1 = P<'x' | 'y'> // number
  type A2 = P<never> // string
条件判断 用于类型的条件判断

extends判断条件的逻辑:如果extends前面的类型可以直接赋值给extends后面的类,那么表达式为真

下面的示例中,DogAnimal的子类型,父类型比子类型的限制更少。能满足子类型,则一定能满足父类型。
Dog类型的值可以赋值给Animal类型的值,判断为真。

可以简单理解为,满足前面类型的数据是否能满足后面的类型。

  // 示例1
  interface Animal {
    eat(): void
  }
  
  interface Dog extends Animal {
    bite(): void
  }
  
  // 如果问号前面的判断为真,则将第一个类型string赋值给A,否则将第二个类型number赋值给A。
  type A = Dog extends Animal ? string : number
  
  const a: A = 'this is string'
应用:Exclude<T,U>排除 / Extract 提取

Exclude<T,U> 排除

语法定义:type Exclude<T, U> = T extends U ? never : T
作用:其作用是从第一个联合类型参数中,将第二个联合类型中出现的联合项全部排除,只留下没有出现过的参数。

示例

type A = Exclude<'key1' | 'key2', 'key2'> // 'key1'
type Exclude<T, U> = T extends U ? never : T

//T为'key1' | 'key2',U为'key2' extends前面的类型是一个泛型,并且是一个联合类型,分配律奏效
type A = ('key1' extends 'key2' ? never : 'key1') | ('key2' extends 'key2' ? never : 'key2')

// =>
type A = 'key1' | never

//never是所有类型的子类型
type A = 'key1'

Extract<T,U> 提取
原理:type Extract<T, U> = T extends U ? T : never
作用:将第二个参数的联合项从第一个参数的联合项中提取出来。(第二个参数可以含有第一个参数没有的项)

type A = Extract<'key1' | 'key2', 'key2'> // 'key2'
type Extract<T, U> = T extends U ? T : never

// T为'key1' | 'key2',U为'key2' extends前面的类型是一个泛型,并且是一个联合类型,分配律奏效
type A = ('key1' extends 'key2' ? 'key1' : never) | ('key2' extends 'key2' ? 'key2' : never)

// =>
type A = never | 'key2'

//never是所有类型的子类型
type A = 'key2'

infer关键词 类型推导

作用:在类型未推导时进行占位,等到真正推导成功后,能准确地返回正确类型。
比如:条件语句 T extends (...args: infer P) => any ? P : T 中,infer P 表示待推断的函数参数。

infer只能在 extends 条件语句中使用,声明变量只能在true分支中使用

// numberPromise 是否可以赋值给 Promise<infer P>  P占位,如果可以则n = P推导出来的类型
type numberPromise = Promise<number>;
type n = numberPromise extends Promise<infer P> ? P : never; // number

keyof 和 in

  • keyof 类型 接受一个对象类型作为参数,返回该对象属性名组成的字面量联合类型。通过 keyof 操作符,可以获取指定类型的所有键
  • in的右侧一般会跟一个联合类型,使用in操作符可以对该联合类型进行迭代。 其作用类似JS中的for...in或者for...of
//keyof使用案例
type Dog = { name: string; age: number;  };
type D = keyof Dog; //type D = "name" | "age"

//in的使用案例
type Animals = 'pig' | 'cat' | 'dog'​
type animals = {
    [key in Animals]: string
}
// type animals = {
//     pig: string; //第一次迭代
//     cat: string; //第二次迭代
//     dog: string; //第三次迭代
// }
  • keyof any返回联合类型string | number | symbol

补充:遇见索引签名时,keyof会直接返回索引的类型。索引类型为string时,keyof返回的类型是string | number,这是因为数字类型索引最终访问时也会被转换为字符串索引类型。

  type Dog = {  [y:number]: number  };
  type dog = keyof Dog;  //type dog = number
  ​
  type Doggy = {  [y:string]: boolean };
  type doggy = keyof Doggy; //type doggy = string | number

使用场景:与extends关键字结合使用,对对象的属性做限定。

泛型约束

对泛型的类型进行约束

确保属性的存在

使用场景:希望变量的类型上存在某属性

interface Length {
  length: number; //这个是特殊用法,表示该变量只要有length就可以,有多余的属性也可以
}
//T extends Length 用于告诉编译器,支持已经实现 Length 接口的任何类型。 接口的继承用法
function identity<T extends Length>(arg: T): T {
  console.log(arg.length); // 可以获取length属性
  return arg;
}
检查对象上的键是否存在

extendskeyof 结合限定类型建 ,表示extends 前面的类型是keyof返回的联合类型中的一个。

extends 不一定要强制满足继承关系,检查是否满足结构兼容性。

// k extends  "name"| "age"  这里确保参数 key 一定是对象中含有的键
function getObjectProperty<O,K extends keyof O>(obj:O,key:K){
    return obj[key]
} 
const info = {
    name:"ranran",
    age:18
}

getObjectProperty(info,"name")

TypeScript映射类型

映射类型:可以理解为将一个类型映射成一个新的类型
使用场景:一个类型需要另一个类型,但是又不想拷贝(映射)一份,可以考虑使用映射类型。

可以将映射类型想象成一个函数,函数的作用就是拷贝(映射)类型

//映射类型MapPerson
//<>定义泛型,接收需要拷贝的类型  
type MapPerson<Type> = {
    //[index:number]:any //索引签名写法
    //Property自定义的标识符, keyof Type表示联合类型,in从联合类型中依次取值赋值给Property
    [Property in keyof Type]:Type[Property]
}
interface Iperson{
    name:string
    age:number
}

type NewPerson = MapPerson<Iperson> //使用映射类型拷贝Iperson类型
映射类型修饰符
  • readonliy 设置属性只读
  • 设置属性可选

可以通过前缀-或者+删除或添加这些修饰符,默认使用+

//映射类型MapPerson
//将所有属性映射为可选属性
type MapPerson<Type> = {
   +readonly [Property in keyof Type]:Type[Property]
}

interface Iperson{
    name:string
    age:number
}

type NewPerson = MapPerson<Iperson>
/*
type NewPerson = {
    readonly name: string;
    readonly age: number;
}
*/

泛型工具类

Partial:将类型里的属性全部变为可选项?

Partial<T> 的作用就是将某个类型里的属性全部变为可选项 ?

Partial<T>的实现可以看成映射类型 + 可选?修饰符

// P为key T为类型对象
//通过 keyof T 拿到 T 的所有属性名,然后使用 in 进行遍历,将值赋给 P,最后通过 T[P] 取得相应的属性值。中间的 ? 号,用于将所有属性变为可选。
type Partial<T> = {
    [P in keyof T]?: T[P];
};
Record:转换类型对象中所有属性的类型

Record<K extends keyof any, T> K 中所有属性的值转化为 T 类型

  • K:创建的新对象需要有哪些属性,属性可以只有一个,也可以有多个,多个属性时采用“联合类型”的写法
    • keyof any返回联合类型string | number | symbolK extends keyof any 表示的是泛型参数 K 必须是一个能作为对象键的类型。
  • T:对象属性的类型。

定义

//[P in K] 表示的是:遍历 K 中的每个键,并将其作为属性名,然后将类型 T 分配给每个属性。
type Record<K extends keyof any, T> = {
    [P in K]: T;
};

示例

interface PageInfo {
  title: string;
}

type Page = "home" | "about" | "contact";
/*
Record<Page, PageInfo>返回
{
  about: { title: string},
  contact: { title: string},
  home: { title: string}
}
*/


const x: Record<Page, PageInfo> = {
  about: { title: '任意字符串'},
  contact: { title: '任意字符串'},
  home: { title: '任意字符串'}
};

使用场景
创建一个特定key并且值类型一样的对象

interface IPerson {
  name: string;
  age: number;
  email: string;
  gender: string;
};

type Person = Record<keyof IPerson, string>

const person: Person = {
  name: "Echo",
  age: "26",
  email: "test@123.com",
  gender: "Male",
};

注意点
1.属性名唯一,如果重复了,后面的键值会覆盖前面的
2.Record<K, T> 创建的对象类型只能包含指定键的属性,不允许存在其他未定义的属性。
3.Record<K, T> 创建的对象类型,如果键包含可选属性,生成的新类型的属性都是必选的。
4.所有属性的属性值都应该具有相同的类型 T,否则 TypeScript 编译器会报错。

Pick:挑选某类型的子属性

Pick<T, K extends keyof T>将某个类型中的子属性挑出来

定义

type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值