我是靠谱客的博主 激动彩虹,这篇文章主要介绍从0开始的 TypeScriptの十三:infer、extends、keyof、typeof、in序,现在分享给大家,希望可以做个参考。

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

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

infer

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

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

extends的示例

复制代码
1
2
3
4
5
6
7
8
9
10
11
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

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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: 解包,获取在数组中的元素类型

复制代码
1
2
3
4
type ParamType<T> = T extends (infer X)[] ? X : never; // c1类型为number | string let c1: ParamType<number[] | string[]> = 10

示例3: 元组tuple转联合union

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

复制代码
1
2
3
4
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的过程就比较复杂了

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

参考文章:

  • https://blog.csdn.net/qq_33221861/article/details/112369522
  • https://juejin.cn/post/6844903796997357582
  • https://blog.csdn.net/weixin_44051815/article/details/124708072
  • https://blog.csdn.net/qq_39920234/article/details/121500009
  • https://www.e-learn.cn/topic/3752913
  • https://zhuanlan.zhihu.com/p/526988538
  • https://www.bookstack.cn/read/TypeScriptDeepDiveZH/59.md

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

复制代码
1
2
3
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):父类型可以赋值给子类型

例子:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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

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

复制代码
1
2
3
4
5
// 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)

复制代码
1
2
3
4
// number | string // (x: ()=> number)=> any | (x: ()=> string)=> any type Union<T> = T extends any ? (x: ()=> T)=> any : never

那么最终的转换方式:

复制代码
1
2
3
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

基本使用:

复制代码
1
2
3
4
5
6
7
8
9
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的联合类型

复制代码
1
2
3
4
5
6
7
8
9
10
11
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

复制代码
1
2
3
4
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是用来判断数据类型,返回成员的类型 可以对对象枚举函数进行类型返回

  • 示例: 对象
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
// typeof 对象 let A = { a: 'aaa', b: 1111 } /* type _A = { a: string; b: number; } */ type _A = typeof A
  • 示例: 类
复制代码
1
2
3
4
5
6
7
8
9
// typeof 类 class C { a: number; b: string } type _C = typeof C let c: _C = C // emmm.... 感觉好像没什么意义

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

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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" 没有构造签名。 }
  • 示例: 枚举
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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 }
  • 示例:函数
复制代码
1
2
3
4
5
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遍历此联合类型,分别对属性名分配类型

复制代码
1
2
3
4
5
6
7
8
9
type A = { name: number; age: number; } // type User = { name: string; age: string; } type User = { [K in keyof A]: string }

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

最后

以上就是激动彩虹最近收集整理的关于从0开始的 TypeScriptの十三:infer、extends、keyof、typeof、in序的全部内容,更多相关从0开始的内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(106)

评论列表共有 0 条评论

立即
投稿
返回
顶部