从0开始的 TypeScriptの十三:infer、extends、keyof、typeof、in

举报
空城机 发表于 2022/07/14 09:06:47 2022/07/14
【摘要】 在 typescript 开发过程中通过泛型进行类型转换是很重要的一块,本文主要讲了 infer、extends、keyof、typeof、in 这五个关键字的使用

在B站看视频学习vue3.0时,有一节主要是使用typescript来配置一些vuex的内容

我看完一遍后,还是有挺多困难点的,首先要去了解一下typescript中的inferkeyof等这些高级用法, 所以本文主要是学习typescript的记录了。

infer

infertypescript中的关键字,可以在extends条件语句中推断待推断的类型,就是从类型中获得类型

(这里的extends不是类、接口的继承,而是对于类型的判断和约束,意思是判断T能否兼容)

extends的示例

type ParamType<T, K> = T extends K ? T : never;

interface Animal {
    animal: string
}
interface Cat {
    cat: string
}
// ParamType的T需要兼容K,否则会出错
let c1: ParamType<Animal | Cat, Cat> = { cat: '猫' }

infer使用

使用方式:

  1. infer只能在extends关键字的右侧
  2. infer x可以理解成一个未知数x,表示待推断的函数参数

示例1: 获取传入的参数类型中的action,如果传入的T中没有action,则会返回never

type ParamType<T> = T extends { action: infer X } ? X : never;

interface Animal {
    animal: string,
    action: void
}
interface Cat {
    cat: string,
    action: ()=>void
}
// c1的类型void | ()=>void
let c1: ParamType<Animal | Cat> = ()=>{
    console.log('打滚');
}
c1() // 打滚

示例2: 解包,获取在数组中的元素类型

type ParamType<T> = T extends (infer X)[] ? X : never;
// c1类型为number | string
let c1: ParamType<number[] | string[]> = 10

示例3: 元组tuple转联合union

其实实现的方式和上面是一样的

type ParamType<T> = T extends (infer X)[] ? X : never;
// c1类型为number | string
let c1: ParamType<[string, number]> = 10

示例4: 联合union转元组tuple

这里将 number | string 转换成 number & string的过程就比较复杂了

在这里我也在网上参考了很多文章,才逐步理解的

参考文章:

如果是想的没那么多,那么可能会像下面这样写:

type Change<T> = T extends infer X | infer Y ? [ X, Y ] : never
type Res = Change<number | string>  // [string, string] | [number, number]

这是因为联合类型会分别进行比较。

首先对于extends左边如果是联合类型union, 那么转换的过程到底应该是怎么样的:

typescript 协变和逆变

这里首先要了解一下typescript的协变和逆变这两个概念

协变(Covariance): 子类型可以赋值给父类型
逆变(Contravariance):父类型可以赋值给子类型

例子:

interface parent {
    a: number,
}
interface child extends parent {
    b: number
}
let p1: parent = {
    a: 1,
}
let p2: child = {
    a: 32,
    b: 7,
}
// 协变,可以将子类型赋给父类型,但不能将父类型赋给子类型
p1 = p2;  
// p2 = p1; 报错
// 逆变,将这个特性放到函数类型当中
type fun1 = (a: parent)=> void
type fun2 = (a: child) => void
type test = fun2 extends fun1 ? true : false

let f1: fun1 = (a: parent)=> {}
let f2: fun2 = (a: child)=>{}

// f1 = f2 报错
f2 = f1

逆变是需要在函数中使用的,除了函数参数类型是逆变,其他都是协变。而在上面联合类型转元组类型中,有一点非常重要,那就是在逆变位置的同一类型变量中的多个候选会被推断成交叉类型

// UnionToTuple = (() => number) & (() => string)
type UnionToTuple = ((x: ()=>number) => any) | ((x: ()=>string) => any) extends (x: infer P)  => any ? P : never
// Res = [number, string]
type Res = UnionToTuple extends { (): infer X; (): infer Y } ? [X, Y] : never

通过逆变可以得到以上的方式,这样最后的[number, string]结果就已经得到了,那现在重要的就是得到((x: ()=>number) => any) | ((x: ()=>string) => any)

这一点就比较容易了,以下方式就可以将number | string变成 ((x: { a: string; }) => any) | ((x: { a: number; }) => any)

// number | string
// (x: ()=> number)=> any | (x: ()=> string)=> any
type Union<T> = T extends any  ? (x: ()=> T)=> any : never

那么最终的转换方式:

type UnionToTuple<T> = ((T extends any  ? (x: ()=> T)=> any : never) extends (x: infer P)  => any ? P : never) extends { (): infer X; (): infer Y } ? [X, Y] : never
type Res = UnionToTuple<number | string>  // [string, number]

emmmm… 这里的转换过程还是特别复杂的,理解起来也比较麻烦,这里最重要的还是在逆变位置的同一类型变量中的多个候选会被推断成交叉类型,这个概念如果不知道,真的很难推导出来




keyof索引类型查询操作符

在上面大致了解了infer后,继续了解泛型高级类型中的keyof,这个其实有点类似于es6中的keys()方法,用于获取键值的遍历器

keyof可以获取某种类型的所有键,返回联合类型union

基本使用:

interface User {
    name: string
    age: number
    action: ()=> void
}

type usertype = keyof User;  // name | age | action
let t1: usertype = "action" 

并且,对于class类来说,keyof只能返回类型上已知的公共属性名,在下面的例子当中,keyof产生的也只是name | age | action的联合类型

class User {
    name: string;
    age: number;
    action: ()=> void;
    private hobby: ()=> string;
    protected eye: string
}
type usertype = keyof User;  // name | age | action
// let t1: usertype = "hobby" // 出错
// let t1: usertype = "eye" // 出错

如果一个类型有一个symbol或者number类型的索引签名,keyof会直接返回这些类型。

这里的索引签名如果是string类型,那么将会返回string | number,这是在Typescript 2.9中新增的内容,可以参考:https://www.bookstack.cn/read/TypeScript-4.4-zh/zh-release-notes-typescript-2.9.md

type K1 = keyof { [x: symbol]: User }; // symbol
type K2 = keyof { [x: number]: User }; // nuumber
type K3 = keyof { [x: string]: User }; // string | number

通常keyof在使用时往往会和in或者typeof搭配使用



typeof

typeof是用来判断数据类型,返回成员的类型 可以对对象枚举函数进行类型返回

  • 示例: 对象
// typeof 对象
let A = {
    a: 'aaa',
    b: 1111
}
/*
type _A = {
    a: string;
    b: number;
}
*/
type _A = typeof A
  • 示例: 类
// typeof 类
class C {
    a: number;
    b: string
}

type _C = typeof C 
let c: _C = C  // emmm.... 感觉好像没什么意义

然后我上网搜索了一下,发现如果是下面这种情况,是需要使用typeof重新获取类的

class Ponit {
    x: number;
    y: number;
    constructor(x: number, y: number) {
      this.x = x;
      this.y = y;
    }
};
// 工厂函数
function getInstance(PointClass: typeof Ponit, x: number, y: number) {
    return new PointClass(x, y);
}
// 下面写法将报错
function getInstance2(PointClass: Ponit, x: number, y: number) {
    return new PointClass(x, y);// 报错 此表达式不可构造。类型 "Ponit" 没有构造签名。
}
  • 示例: 枚举
// typeof 枚举
// 使用枚举限定日期
enum day { Mon, Tue, Wed, Thu, Fri, Sat, Sun}
type _day = typeof day;
let days: _day = {
    Mon: 4,
    Tue: 12,
    Wed: 1, 
    Thu: 1, 
    Fri: 1, 
    Sat: 1, 
    Sun: 1
}
console.log(days); // { Mon: 4, Tue: 12, Wed: 1, Thu: 1, Fri: 1, Sat: 1, Sun: 1 }
  • 示例:函数
function compare(x: number, y: number):boolean {
    return x > y;
}
type _compare = typeof compare;  // (x: number, y: number) => boolean


in类型映射

对于类型,同样也可以进行遍历枚举,使用的方式就是in关键字

使用方式: [ K in Keys ] , 这里的Keys必须是string,number,symbol或者联合类型

示例:将type A = { name: number; age: number; } 内部类型全部从number转变为string

运营之前学到的keyof,将类型A转变为name | age, 然后再使用in遍历此联合类型,分别对属性名分配类型

type A = {
    name: number;
    age: number;
}
// type User = { name: string; age: string; }
type User = {
    [K in keyof A]: string
}

其实关于泛型的类型转换还有内置类型可以使用,这里就先不说明了

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。