共计 3597 个字符,预计需要花费 9 分钟才能阅读完成。
明天学习 TypeScript 的一种新的转换类型操作:索引映射类型——IIMT(Immediately Indexed Mapped Type)
。
这个类型特地有意思,咱们先看一个示例:
type Tuple = [1, true, 'false'] | |
type List = Tuple[number] |
- 下面代码先创立了一个元组 Tuple。
- 而后通过 number 进行索引拜访
失去的 List 为:
type List = 1 | true | 'false'
能够发现下面操作 通过索引映射,将操作类型转为了联结类型。
如果是对象类型呢?
type Person = { | |
name: string | |
age: number | |
} | |
type GetValueType = {[K in keyof Person]: Person[K] | |
}[keyof Person] |
通过下面的转换,咱们失去一个新的类型:
type GetValueType = string | number
如果说咱们想依据 Person 类型创立一个新的类型,其构造为:
/** | |
* | { | |
* key: string; | |
* } | { | |
* key: number; | |
* } | |
* | |
*/ |
批改 GetValueType:
type GetValueType = {[K in keyof Person]: {key: Person[K] | |
} | |
}[keyof Person] |
下面的过程,能够合成为:
- 先创立一个下列构造的类型
/** | |
* { | |
* name: { | |
* key: string; | |
* }, | |
* age: { | |
* key: number; | |
* } | |
* } | |
*/ | |
type Temp = {[K in keyof Person]: {key: Person[K] | |
} | |
} |
- 通过 IIMT 创立指标类型
type GetValueType = Temp[keyof Person]
通过下面两个示例,能够发现所谓的 IIMT,就是 通过索引拜访类型操作去迭代指标类型的 key 并为每个 key 创立一个新的类型。
通过 IIMT 遍历联结类型
IIMT 的特点在于,当你遍历联结类型的所有成员时,同时能够为你保留整个联结类型的上下文,不至于在遍历过程中失落。上面咱们基于一个联结类型创立一个新的联结类型
type Fruit = "apple" | "banana" | "orange"; | |
/** | |
* | { | |
* thisFruit: 'apple'; | |
* allFruit: 'apple' | 'banana' | 'orange'; | |
* } | |
* | { | |
* thisFruit: 'banana'; | |
* allFruit: 'apple' | 'banana' | 'orange'; | |
* } | |
* | { | |
* thisFruit: 'orange'; | |
* allFruit: 'apple' | 'banana' | 'orange'; | |
* } | |
*/ | |
export type FruitInfo = {[F in Fruit]: { | |
thisFruit: F; | |
allFruit: Fruit; | |
}; | |
}[Fruit]; |
如果不应用 IIMT,则下面会创立一个新的 对象类型。
咱们能够看到新创建的 FruitInfo 类型是三个对象的联结类型,每个对象都有一个 thisFruit 属性和一个 allFruit 属性。thisFruit 属性是联结类型的特定成员,而 allFruit 属性是整个联结类型。
如果再退出其余工具类型,则又能够玩出许多花活,比方咱们当初要实现在 allFruit 属性中,剔除 thisFruit,就能够这么写:
/** | |
* | { | |
* thisFruit: 'apple'; | |
* allFruit: 'banana' | 'orange'; | |
* } | |
* | { | |
* thisFruit: 'banana'; | |
* allFruit: 'apple' | 'orange'; | |
* } | |
* | { | |
* thisFruit: 'orange'; | |
* allFruit: 'apple' | 'banana'; | |
* } | |
*/ | |
export type FruitInfo = {[F in Fruit]: { | |
thisFruit: F; | |
allFruit: Exclude<Fruit, F>; | |
}; | |
}[Fruit]; |
下面的代码在迭代 Fruit 的时候,每次都会传入一个残缺的 Fruit 类型,也就是说每次 F 每次所能感知到的 Fruit 都是独立的、互不烦扰的,所以能够应用 Exclude 从联结类型中删除以后的 F。
转换对象类型的联结类型
IIMT 常常用于操作对象类型的联结类型,比方须要对联结类型中的每个对象的属性进行批改。
比方须要对 Event 类型的每个对象类型的 type 属性加个前缀。
type Event = | |
| { | |
type: "click"; | |
x: number; | |
y: number; | |
} | |
| { | |
type: "hover"; | |
element: HTMLElement; | |
}; |
如果说想间接通过遍历去批改联结类型中的对象类型,那么 ts 编译器会提醒报错,比如说像上面这样:
type Example = { | |
// Type 'Event' is not assignable to | |
// type 'string | number | symbol'. | |
[E in Event]: {};}; |
因为你通过 E in Event 遍历失去的其实是 每个对象类型,并不是 ‘string | number | symbol’。
这里咱们能够通过 as 关键字在映射类型中操作类型
// 通过 Omit 剔除 type 类型,对 type 属性独自操作 | |
// 通过 穿插类型实现 更改 type 属性 | |
type PrefixType<E extends {type: string}> = {type: `PREFIX_${E["type"]}`; | |
} & Omit<E, "type">; | |
/** | |
* | { | |
* type: 'PREFIX_click'; | |
* x: number; | |
* y: number; | |
* } | |
* | { | |
* type: 'PREFIX_hover'; | |
* element: HTMLElement; | |
* } | |
*/ | |
type Example = {[E in Event as E["type"]]: PrefixType<E>; | |
}[Event["type"]]; |
在这里,咱们插入 as E[‘type’] 将键从新映射为咱们想要的类型。而后应用 PrefixType 为每个对象的 type 属性增加前缀。
最初,咱们 应用 Event[‘type’] 索引到映射类型,也就是 click | hover——这样咱们就失去了带前缀的对象的联结。
再看一些例子:
- 转 css 单位为联结对象类型
type CSSUnits = "px" | "em" | "rem" | "vw" | "vh"; | |
/** | |
* | { | |
* length: number; | |
* unit: 'px'; | |
* } | |
* | { | |
* length: number; | |
* unit: 'em'; | |
* } | |
* | { | |
* length: number; | |
* unit: 'rem'; | |
* } | |
* | { | |
* length: number; | |
* unit: 'vw'; | |
* } | |
* | { | |
* length: number; | |
* unit: 'vh'; | |
* } | |
*/ | |
export type CSSLength = {[U in CSSUnits]: { | |
length: number; | |
unit: U; | |
}; | |
}[CSSUnits]; |
- 转 http 响应码与状态为联结类型
type SuccessResponseCode = 200; | |
type ErrorResponseCode = 400 | 500; | |
type ResponseCode = | |
| SuccessResponseCode | |
| ErrorResponseCode; | |
/** | |
* | { | |
* code: 200; | |
* body: { | |
* success: true; | |
* }; | |
* } | |
* | { | |
* code: 400; | |
* body: { | |
* success: false; | |
* error: string; | |
* }; | |
* } | |
* | { | |
* code: 500; | |
* body: { | |
* success: false; | |
* error: string; | |
* }; | |
* } | |
*/ | |
type ResponseShape = {[C in ResponseCode]: { | |
code: C; | |
body: C extends SuccessResponseCode | |
? {success: true} | |
: {success: false; error: string}; | |
}; | |
}[ResponseCode]; | |
— |
总结
在 ts 中如果绝对联结类型进行遍历操作,相较于官网文档中提到的根底操作,并不能很好的实现。然而 IIMT 就能够解决这个问题,相当于 typscript 通过 IIMT 提供了一个对联结类型遍历的能力,通过 IIMT 能够实现对联结类型的单个类型进行操作,再联合其余类型体操根底动作,又能够玩出许多花色来。
参考:
- Indexed Access Types: https://www.typescriptlang.org/docs/handbook/2/indexed-access…
- Key Remapping via
as
:https://www.typescriptlang.org/docs/handbook/2/mapped-types.h… - Transform Any Union in TypeScript with the IIMT:https://www.totaltypescript.com/immediately-indexed-mapped-type