[Day15] TS:在 Mapped Type 中使用 Template Literal 来改物件型别中的所有 key

Mapped Types

上面这个是今天会提到的内容,如果你已经可以轻松看懂,欢迎直接左转去看我队友们的精彩文章!

昨天我们已经学到了 Mapped Types 的精华,就是可以在 Index Signatures 中使用 in 来叠代 in 後面的所有元素,便可以透过 Mapped Type 修改物件型别中所有属性值的型别。因此,可以把原本的 SupportedEvent 进行修改:

type SupportedEvent = {
  click: string;
  change: string;
  keyup: string;
  keydown: string;
};

透过 HandledEvent 这个 Utility Type 进行转换後,产生出新的型别:

type HandledEvent = {
  [K in keyof SupportedEvent]: () => void;
};

更好的方式还可以把 Supported Event 变成一个泛型的参数,让它变成 Utility Type,像是这样,如此 MappedValuesToFunction 就可以变的更泛用:

type MappedValuesToFunction<T> = {
  [K in keyof T]: () => void;
};
type HandledEvent = MappedValueToFunction<SupportedEvent>;

修改物件型别的所有 key

从昨天的内容中,我们已经知道如何透过 Mapped Types 「一次修改所有 SupportedEvent 属性值的型别」,最後得到的 HandledEvent 会是:

type HandledEvent = {
  click: () => void;
  change: () => void;
  keyup: () => void;
  keydown: () => void;
};

如果现在我们不是要修改物件型别中属性值的型别,而是想要修改属性 key 的名称时,可以怎麽做呢?举例来说,我们想要根据 HandledEvent ,透过某个 Utility Type 後可以产生出像这样的型别:

type EventHandler = {
  handleClick: () => void;
  handleChange: () => void;
  handleKeyup: () => void;
  handleKeydown: () => void;
};

这时候就可以用到今天要提的这个 Utility Type:

Mapped Types

让我们来依序拆解并理解这个写法。

Mapped Type + keyof Type Operator

Mapped Types

首先,从 in 这个关键字可以看到这里用了 Mapped Type,而 [K in keyof T] 的意思就是把 T 这个物件型别的所有 key 取出组成 union types,以这里来说,如果写 ToEventHandler<HandledEvent> 的话,这里的 keyof T 应该就会变成 "click" | "change" | "keyup" | "keydown"

keyof

所以说 [K in keyof T] 就会是用叠代的方式,每次取出 "click" | "change" | "keyup" | "keydown" 中的元素来跑回圈,可以想像组出来的东西应该会像这样:

type HandledEvent = {
  click: '...';
  change: '...';
  keyup: '...';
  keydown: '...';
};

搭配 as 使用 Template Literal

接着我们看到这里有 Template Literal 的用法,也就是 `${}` 的用法,并且搭配关键字 as 来使用:

Mapped Type

透过 as 後面接字串型别的方式,就可以让我们达到修改属性 key 的目的。

现在为了方便理解,我们先把 Template Literal 中的 Capitalize 拿掉:

type ToEventHandler<T> = {
  [K in keyof T as `handle${string & K}`]: T[K];
};

这时候应该会看到经过这个 Utility Type 後跑出来的型别会是:

type EventHandler = ToEventHandler<HandledEvent>;

// 预期 EventHandler 会长这样
type EventHandler = {
  handleclick: '...';
  handlechange: '...';
  handlekeyup: '...';
  handlekeydown: '...';
};

这里我们已经成功透过 Mapped Type 搭配 as 和 Literal Template 的使用,成功把物件型别的 key 进行了转换,让每个 key 的最前面都多了 handle... 的前缀。

但因为在 JavaScript 中,通常函式的命名会用小写驼峰的方式,例如,handleClickhandleChange,因此这里可以在搭配使用在 Day12 时曾经提到的 Intrinsic String Manipulation Types ,透过 Capitalize 来将字串型别的第一个字转成英文大写,因此如果改会原本的写法 as `handle${Capitalize<string & K>}`後,产生出来的 key 的名称就会是我们想要的以 handle 作为开头的 key:

type EventHandler = ToEventHandler<HandledEvent>;

// 预期 EventHandler 会长这样
type EventHandler = {
  handleClick: '...';
  handleChange: '...';
  handleKeyup: '...';
  handleKeydown: '...';
};

Mapped Type 中的 K (key) 是可以拿来使用的

来看最後一个部分:

Mapped Types

这里我们把 Mapped Types 跑回圈是的变数取名为 K,而不论取名是 KP,它都只是个变数名称,要取名成什麽都可以,但实际上它也不单单只是个名称,它还可以被拿来被後续使用。

这里 [...]: T[K]: 後面放的就是这个属性值的型别,我们知道 T 就是我们带进去的物件型别,而 K 其实就是没有次跑回圈时的 key,因此 T[K] 就是我们在 Day05 曾提过的 Indexed Access Types,意思就是直接把该属性值原本的型别取出来就好。

总结

现在总结来看,应该就可以知道怎麽透过 Mapped Type 搭配 as 和 Template Literal 来修改物件型别中的属性名称:

type SupportedEvent = {
  click: string;
  change: string;
  keyup: string;
  keydown: string;
};

type MappedValuesToFunction<T> = {
  [K in keyof T]: () => void;
};
type HandledEvent = MappedValuesToFunction<SupportedEvent>;

type ToEventHandler<T> = {
  [K in keyof T as `handle${Capitalize<string & K>}`]: T[K];
};
type EventHandler = ToEventHandler<HandledEvent>;

这里最终的 EventHandler 就会是:

type EventHandler = {
  handleClick: () => void;
  handleChange: () => void;
  handleKeyup: () => void;
  handleKeydown: () => void;
};

范例程序码

https://tsplay.dev/mAVZRW @ TypeScript Playground

参考资料


<<:  【Day 15】- 今天来实作一个 Kernel mode Thread

>>:  2.4.6 Design System - Carousel

Day 19: 人工智慧初探 优化器的作用

Optimizer 优化器 神经网路是由多个神经元节点组成,每个神经元(Neuron)都拥有自己的权...

[day14]Vue.js 网站基本建置

其实今年才刚学Vue.js ,目前的程度大概就是写几个简单的功能而已,要写一个比较完整的网站还是十分...

D3JsDay17 Fill the color,Zoom in on center—地图各项操作及填色

改变path的样貌 首先观看以下程序码 const width = 800; const heigh...

Day 24:605. Can Place Flowers

今日题目 题目连结:605. Can Place Flowers 题目主题:Array, Greed...

利用网页浏览器执行Raspberry Pi的程序

前面有提到Raspberry pi有网路的功能 既然有网路 就可以将感测器所计算出来的数据 传送到云...