从0开始的 TypeScriptの十三:inferextendskeyoftypeofin

Posted 空城机

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从0开始的 TypeScriptの十三:inferextendskeyoftypeofin相关的知识,希望对你有一定的参考价值。

在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

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

以上是关于从0开始的 TypeScriptの十三:inferextendskeyoftypeofin的主要内容,如果未能解决你的问题,请参考以下文章

从0开始的 TypeScriptの十三:inferextendskeyoftypeofin

从0开始的 TypeScriptの十四:内置工具类型

从0开始的 TypeScriptの十四:内置工具类型

从0开始的 TypeScriptの十四:内置工具类型

从0开始的TypeScriptの十:泛型

从0开始的TypeScriptの十:泛型