某一天,一个朋友忽然在微信里问我,“['TYPE'] => { 'TYPE': 'TYPE' },写个函数转换这个,类型怎么加?”。笔者立刻的回复就是,“用 reduce 将数组转对象,TS 类型写个泛型便可”。html
说是这么说,可是发如今写 TS 类型上实际上却的确不是那么容易,因而,笔者立刻偷偷开始尝试。(推荐你们能够在 TypeScript Playground 上练习 TS 类型)typescript
首先转换从数组转换到对象,咱们用 reduce 很容易达成目标:数组
function convert(source) {
return source.reduce((memo, key) => {
return {
...memo,
[key]: key
}
}, {});
}
复制代码
但本文的重点是写这个函数的类型。简而言之,就是须要写 convert 函数参数的类型和返回值类型,从而能够定义函数的形状。微信
因而,笔者折腾了一会,写出来下述类型,编辑器
function convert<K extends string>(source: K[]): Record<K, K> {
return source.reduce((memo, key) => {
return {
...memo,
[key]: key
}
}, {} as any);
}
type TNAME = 'LIN' | 'HUA';
const peoples: TNAME[] = ['LIN', 'HUA'];
const peopleMap = convert<TNAME>(peoples);
function getNameFromPeopleMap<T extends any>(name: T): T {
return peopleMap[name];
}
const t = getNameFromPeopleMap('HUA');
复制代码
新增一个 getNameFromPeopleMap 函数,编写泛型 (name: T): T。从而达成了如下效果:
函数
因此传入任意值,编辑器都不会提示类型错误。
优化
所以,上述类型若是打个分,连及格线都不达。因而,进行考虑优化。为了解决上述两个问题,一段时间后,新的类型火热出炉!ui
function convert<K extends string>(source: readonly K[]): Record<K, K> {
return source.reduce((memo, key) => {
return {
...memo,
[key]: key
}
}, {} as any);
}
const peoples = ['LIN', 'HUA'] as const;
const peopleMap = convert<typeof peoples[number]>(peoples);
function getNameFromPeopleMap<T extends typeof peoples[number]>(name: T): T {
return peopleMap[name] as any;
}
const t = getNameFromPeopleMap('LIN');
复制代码
为了减小冗余的 TNAME。使用了 TypeScript 3.4 版本新增的 const 断言。它能够将咱们一个字面量表达式断言为一个 TS 类型。以下:spa
// Type '"HELLO"'
const STR = "HELLO" as const;
// Type 'readonly [10, 20]'
const POINT = [10, 20] as const;
// Type '{ readonly text: "hello" }'
const PAYLOAD = { text: "hello" } as const;
复制代码
所以,无需 TNAME[],咱们的 peoples 拥有了 TS 类型。
3d
同时,修改 getNameFromPeopleMap 的函数泛型声明为 <T extends typeof peoples[number]>(name: T): T。咱们便可约束函数传入值只能为 peoples 的子项。
可能有些同窗比较困惑,上述泛型是什么意思。笔者这里稍微解释一下。
那么 T extends 即表明着进行泛型约束。所以咱们就能够成功对函数传参进行类型限制。
瞧一瞧,多么成功。当我自信的把这段代码发给朋友时,又遭到了无情吐槽,“我想要的是 peopleMap 直接使用”
的确,笔者反思了一下,为何须要莫名其妙多一个函数去取值呢?还不是由于没法从 convert 函数上完成传参值和返回值的相等的类型限制嘛。
嗯,若是能从 convert 函数上直接支持就行了。
因而笔者将目光挪到了可疑的 convert 函数类型 (source: readonly K[]): Record<K, K> 上。首先从 Record<K, K> 上看,已经没法约束类型了。按照需求,返回值应该是 key 与 value 相等的对象类型。怎么写呢?笔者查阅了 TS 文档后,得出一种写法:
type IRecord<K extends keyof any> = {
[P in K]: P;
}
复制代码
可能你们好奇 keyof any 是什么?其实它刚恰好就是对象的 key 类型。由于 TS 限制对象 key 类型只能为 string、number、symbol。
使用效果以下:
所以最终能够干掉以前多余的函数,得出以下类型:
type IRecord<K extends keyof any> = {
[P in K]: P;
}
function convert<K extends string>(source: readonly K[]): IRecord<K> {
return source.reduce((memo, key) => {
return {
...memo,
[key]: key
}
}, {} as any);
}
const peoples = ['LIN', 'HUA'] as const;
const peopleMap = convert<typeof peoples[number]>(peoples);
const t = peopleMap.LIN;
复制代码
同时也达到了对 peopleMap 的取值类型限制。
function convert<K extends keyof any>(source: readonly K[]): { [P in K]: P }
{
return source.reduce((memo, key) => {
return {
...memo,
[key]: key
}
}, {} as any);
}
const peoples = ['LIN', 'HUA'] as const;
const peopleMap = convert(peoples);
const t = peopleMap.LIN;
复制代码
简简单单的一个 TS 类型的需求,发现编写起来仍是挺有意思的。第一,做为笔记;第二,也但愿能够帮助到有须要的同窗们。
谢谢你们~若有助益,不胜荣幸!