返回

TypeScript Union Type: 可选 Key 实现技巧

javascript

TypeScript 中 Union Type 的可选 Key 实现

在使用 TypeScript 构建类型安全的应用时,经常会遇到需要在一个联合类型(Union Type)中将某个 key 设置为可选的情况。通常是因为业务逻辑发生改变,导致某些字段不是在所有情况下都必须存在。以下介绍几种常见的方式来解决这个问题。

使用 Partial 和 Union Type 的组合

Partial<T> 是 TypeScript 提供的一个泛型类型,可以将类型 T 的所有属性变为可选。通过结合 Partial 和 Union Type,可以将指定的 key 设置为可选。

原理:

Partial 创建一个新类型,其中原始类型的所有属性都变为可选。随后,利用 Union Type 创建两种类型选择:原始类型(带必须 key)和 Partial 类型(可选 key)。

实现方式:

  1. 首先,定义你的 Union Type:

    export type Types = "APPLE" | "MANGO" | "BANANA";
    
  2. 定义一个类型,除了你想设置为可选的 Key 外,其他的 Key 都必须存在。

type OptionalBanana =
T extends "BANANA" ? T : never;


3. 定义你的 Mapping type 加上 Partial,可以这么操作。
```typescript
type MyMapping = { [key in Types]: string } & Partial<{ [K in OptionalBanana<Types>]: string }>;

 const MAPPING: MyMapping = {
 APPLE: "Here is Apple",
 MANGO: "Here is Mango",
 };

 const MAPPING_BANANA: MyMapping = {
 APPLE: "Here is Apple",
 MANGO: "Here is Mango",
 BANANA:"Here is BANANA",
 };

代码示例:

使用 OmitPick

Omit<T, K> 从类型 T 中排除键 KPick<T, K> 则从类型 T 中选择键 K。可以结合两者来创建一个新的类型,使其仅在某些条件下包含特定的键。

原理:

使用 Pick 创建一个仅包含要设为可选的 key 的类型。使用 Omit 剔除这个 key 创建另一个类型。最后,将这两个类型合并为一个新的类型,该类型中指定的 key 是可选的。

实现方式:

  1. 定义一个类型,用来Pick 可选的key。
  type BananaType = Pick<{ [key in Types]: string }, 'BANANA'>;
  1. 定义一个新的Mapping Type。

type MappingWithoutBanana = Omit<{ [key in Types]: string }, 'BANANA'>


3. 合并两种Type成一个新的 Type。

```typescript
type NewMapping = MappingWithoutBanana & Partial<BananaType>;

代码示例:

export type Types = "APPLE" | "MANGO" | "BANANA";

type BananaType = Pick<{ [key in Types]: string }, 'BANANA'>;
type MappingWithoutBanana = Omit<{ [key in Types]: string }, 'BANANA'>

type NewMapping = MappingWithoutBanana & Partial<BananaType>;

const MAPPING: NewMapping = {
  APPLE: "Here is Apple",
  MANGO: "Here is Mango",
};

const MAPPING_BANANA: NewMapping = {
 APPLE: "Here is Apple",
 MANGO: "Here is Mango",
 BANANA: "Here is Banana"
};

优点: 这种方法相比于直接使用 Exclude,在一些较为复杂的类型场景下可能更容易理解和维护。它通过明确地提取和排除属性,使得类型转换的过程更为清晰。

扩展现有类型

有时候可以通过扩展现有类型来实现目的,这种方式更像是一种曲线救国的方法,适用于不方便直接修改原始 Union Type 定义的情况。

原理:

创建一个新的类型,继承自原始类型,并且将指定的 key 设置为可选。通过使用 & 运算符进行交叉类型合并。

实现方式:

  1. 创建一个 Type 添加 Optional BANANA。

type OptionalType = { BANANA?: string };

2. 创建一个Type继承 MAPPING 加上Optional 的值。

 ```typescript
 type MyMapping = { [key in Types]: string } & OptionalType;
 ```

**代码示例:** 

```typescript
export type Types = "APPLE" | "MANGO" | "BANANA";

type OptionalType = { BANANA?: string };

type MyMapping = { [key in Types]: string } & OptionalType;

const MAPPING: MyMapping = {
 APPLE: "Here is Apple",
 MANGO: "Here is Mango",
};

const MAPPING_BANANA: MyMapping = {
 APPLE: "Here is Apple",
 MANGO: "Here is Mango",
 BANANA:"Here is Banana"
};

注意事项: 使用类型断言时要格外小心,确保值的实际类型与断言的类型一致,避免在运行时出现错误。如果有可能,尽量使用类型守卫或者其他更安全的方式来确保类型的正确性。

以上提供的几种方法都可以实现在 Union Type 中将 key 设置为可选的目的。具体使用哪种方式,需要根据实际的项目情况、团队的编码习惯以及对类型安全的要求来选择。