[Day16] TS:在 Mapped Type 中修改物件的 property modifiers:理解 Partial、Required 和 Readonly 的实作

Required

这是我们今天要聊的内容,老样的,如果你已经可以轻松看懂,欢迎直接左转去看同事 Kyle 的精彩文章 — 「今晚,我想来点 Web 前端效能优化大补帖!」。

Day14 我们理解了如何透过 Mapped Type 修改物件型别中属性的 value,在 Day15 中我们则理解了如何透过 Mapped Type 来修改物件型别中属性的 key,看起来好像已经能完整操作物件型别了,但等等,在 TypeScript 的物件型别中还有一个是 Property Modifiers(属性修饰符)。

Property Modifiers

属性修饰符(Property Modifiers)这个词你可能没听过,但应该一定用过,像是透过在属性名称後面加上 ? 让该属性变成是 optional 的,或是透过 readonly 让该属性在 type-checking 时不能被修改,另外像是 Day14 提过的 Index Signatures 也算是 Property Modifiers 的一种:

// Property Modifiers: ?, readonly
interface Person {
  firstName: string;
  lastName?: string; // lastName is optional
  readonly age: number; // you should not mutate the age
}

Mapped Modifiers

在知道 Property Modifiers 後,让我们来看看如何透过 Mapped Types 来修改属性的 Property Modifiers。

如果要在 Mapped Types 中添加或移除 Property Modifiers 的话(像是把所有的属性都变成 optional),需要用到 +-,预设没写的话就是 + ,来看下面几个例子。

Optional Properties

要改变物件属性是否为 optional,只需要使用 +?-? 即可。若要把每个物件型别的属型都变 optional,可以使用 +?? 都可以,因为预设就会是 +

Mapped Modifiers

相反的如果要让物件型别全部的属性都不是 optional 的话,可以用 -?,但这里的减号就不能省略:

Mapped Modifiers

实际上使用这两个 Type Utility 的话,效果会像是这样:

interface Person {
  firstName: string;
  lastName?: string; // lastName is optional
  readonly age: number; // you should not mutate the age
}

// 把每个物件型别的属性都变成 optional
type ToOptionalProperty<T> = {
  [K in keyof T]+?: T[K];
};

// 把每个物件型别的属性的 optional 都移除
type RemoveOptionalProperty<T> = {
  [K in keyof T]-?: T[K];
};

type PersonWithOptionalProps = ToOptionalProperty<Person>;
type PersonWithoutOptionalProps = RemoveOptionalProperty<Person>;

一开始 Person 中并不是每个 Property 都是 optional 的,但经过 ToOptionalProperty,所有 Properties 就都会是 Optional 的:

Mapped Modifiers

RemoveOptionalProperty 则能够以此类推,使用 -? 後所有物件属性原本的 optional 也都会被移除。

Readonly Properties

使用 Mapped Modifiers 添加或移除 readonly 属性的方式也完全一样,也是透过 +-

// 把每个物件型别的属性都加上 readonly
type ToReadOnlyProperty<T> = {
  +readonly [K in keyof T]: T[K]; // 最前面的 + 可以省略
};

// 把每个物件型别的属性都移除 readonly
type RemoveReadOnlyProperty<T> = {
  -readonly [K in keyof T]: T[K];
};

type PersonWithReadonlyProps = ToReadonlyProperty<Person>;
type PersonWithoutReadonlyProps = RemoveReadonlyProperty<Person>;

使用起来的效果就和 optional 时的说明一样,如果针对 Person 使用了 RemoveReadonlyProperty 的话,原本的 readonly modifier 会被移除:

Mapped Modifiers

根据 Mapped Modifiers 实作出来的 Utility Types

实际上根据笔者个人经验,比较少直接用的 Mapped Modifiers 来操作,不是它们不常被用到,而是因为 TypeScript 已经把它们包成几个常用的 Utility Types,像是 Partial<Type>Required<Type>Readonly<Type> ,这三个 Type Utilities 都是在 Mapped Types 中使用 Mapped Modifiers 来做的操作,未来会再看到更多 Mapped Types 的延伸变化,就能理解 Mapped Type 有多强大。

有了 Mapped Modifiers 的概念後,这几个 Utility Types 的原始码都会很好理解。其中最常用的 Partial<Type> 其原始码是:

// Make all properties in T optional
type Partial<T> = {
  [P in keyof T]?: T[P];
};

跟我们刚刚写的 ToOptionalProperty 是不是一模一样呢?

然後是 Required<Type> 的原始码:

// Make all properties in T required
type Required<T> = {
  [P in keyof T]-?: T[P];
};

是不是也和刚刚写的 RemoveOptionalProperty 一样?

最後是 Readonly<Type> 的原始码:

type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

也和刚刚我们提到的 ToReadOnlyProperty 相同。

范例程序码

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

参考资料


<<:  [Day16]汇总函数实作

>>:  [Day 19] 自动化机器学习 - AutoML

3.1 Design System - 管理平台

万事起头难 爬山一开始总会比较喘、比较累 但逐渐靠近山头後 身体就慢慢适应环境了 登山者也较能欣赏...

成为 Scrum Master:更基本的部分

前言 接着昨天分享的话题,针对成为 Scrum Master 的经历与想法再进行补充。 大家从标题「...

AI ninja project [day 11] 图片分类(1)

我想先回来介绍一些深度学习框架好了, 非类神经的演算法也可以被归为AI, 但是神经网路的深度学习算是...

[Day22] 在 Codecademy 学 React ~ 原来 useState 就是 this.state + this.setState 啊!

前言 其实你知道吗? 今天要讲的 this.state this.setState 其实就是之前介绍...

Day6-我通知你的通知通知我!!!(无误!

标题那个还真的是没有写错~ 且听我细细道来~ ------------------------ 【一...