Yuzhe's Blog

yuzhes

ObjectEntries

ObjectEntries

题目链接

题目

实现类型版本的 Object.entries

interface Model {
  name: string;
  age: number;
  locations: string[] | null;
}

type modelEntries = ObjectEntries<Model>;
// ['name', string] | ['age', number] | ['locations', string[] | null]

解答

思路

核心思路是 Object.entries 返回键值对数组。在 TypeScript 类型系统中,俺们需要:

  1. 遍历对象的每个键
  2. 为每个属性创建元组 [Key, Value]
  3. 返回所有元组的联合类型

难点在于处理可选属性。当属性是可选的 (key?: Type) 时,其值类型变为 Type | undefined。但 Object.entries 实际上不会包含这个 undefined —— 它只是可能不包含这个条目。

基础解法

type ObjectEntries<T> = {
  [K in keyof T]-?: [K, T[K]]
}[keyof T]

等等 —— 这还不太对。-? 移除了可选修饰符,但 T[K] 对于可选属性仍然包含 undefined

处理可选属性

俺们需要从来自可选属性的值类型中剥离 undefined

type ObjectEntries<T> = {
  [K in keyof T]-?: [K, T[K] extends undefined ? undefined : Exclude<T[K], undefined>]
}[keyof T]

但这有个问题 —— 如果 undefined 是类型的显式部分(而非来自可选性)呢?

最终解法

type ObjectEntries<T, U = Required<T>> = {
  [K in keyof U]: [K, U[K]]
}[keyof U]

通过使用 Required<T>,俺们:

  1. 移除所有可选修饰符
  2. 这也移除了因可选性添加的 undefined
  3. 保留联合类型中显式的 undefined(如 string | undefined

不过这种方法会把显式声明的 undefined 也去掉。更精确的解法:

type RemoveUndefined<T> = [T] extends [undefined] ? T : Exclude<T, undefined>

type ObjectEntries<T> = {
  [K in keyof T]-?: [K, RemoveUndefined<T[K]>]
}[keyof T]

这样只有当类型仅为 undefined 时才保留它。

深入分析

为什么用 -? 修饰符?

-? 修饰符移除属性的可选标记。没有它,可选属性会创建类似 ['key', Type | undefined] 的条目。

映射类型转联合

{ [K in keyof T]: ... }[keyof T] 是一个常见模式:

  1. 首先创建一个转换后值的映射类型
  2. 然后用 keyof T 索引它,得到所有值的联合类型

边界情况

考虑这些场景:

要点总结

  1. -? 移除可选性 —— 正确类型化条目的关键
  2. 映射类型转联合模式 —— { [K in keyof T]: F<K> }[keyof T] 转换为联合
  3. 可选 vs 显式 undefined —— 它们行为不同,需要仔细处理
  4. Required<T> 同时移除 ? 和它添加的 undefined