type-challenges:ObjectEntries

11 阅读2分钟

ObjectEntries

问题描述

实现 Object.entry 的类型版本。

举例:

interface Model {
  name: string;
  age: number;
  locations: string[] | null;
}
type modelEntries = ObjectEntries<Model> // ['name', string] | ['age', number] | ['locations', string[] | null];
// ============= Test Cases =============
import type { Equal, Expect } from './test-utils'interface Model {
  name: string
  age: number
  locations: string[] | null
}
​
type ModelEntries = ['name', string] | ['age', number] | ['locations', string[] | null]
​
type cases = [
  Expect<Equal<ObjectEntries<Model>, ModelEntries>>,
  Expect<Equal<ObjectEntries<Partial<Model>>, ModelEntries>>,
  Expect<Equal<ObjectEntries<{ key?: undefined }>, ['key', undefined]>>,
  Expect<Equal<ObjectEntries<{ key: undefined }>, ['key', undefined]>>,
  Expect<Equal<ObjectEntries<{ key: string | undefined }>, ['key', string | undefined]>>
]
​
// ============= Your Code Here =============
// 答案1
type ObjectEntries<T, U = Required<T>> = {
  [K in keyof U]: [K, U[K] extends never ? undefined : U[K]]
}[keyof U]
​
// 答案2
type NeverToUndefined<T> = [T] extends [never] ? undefined : T
type ObjectEntries<T, S extends keyof T = keyof T> = S extends S ? [S, NeverToUndefined<Required<T>[S]>] : never

答案1

这题需要解决的问题是如何将对象转换成联合类型

数组转联合类型用 [number] 作为下标

['1', '2']['number'] // '1' | '2'

对象则是用 [keyof T] 作为下标

type ObjectToUnion<T> = T[keyof T]

看回本题,联合类型的每一项都是数组,因此只需要构造符合结构的对象即可,因为 value 有可能是 undefined ,所以将泛型的参数全变为必选 Required<T>,然而,Required<{ key?: undefined }> 将会得到 {key: never},所以这里需要加一个条件判断 U[K] extends never ? undefined : U[K]never 变为 undefined

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

答案2

要求返回联合类型,想到可以利用 分布式条件类型(Distributive Conditional Types), 那么就需要一个 T extends T, 我们可以添加一个参数 S, 默认值为 keyof T:

type ObjectEntries<T, S = keyof T> = S extends S ? /** ... */ : never

那么 /** ... */ 这里其实很明显了,就是 [S, T[S]], 不过要限制一下 Skeyof T 类型,否则用 T[S] 会报错:

type ObjectEntries<T, S extends keyof T = keyof T> = S extends S ? [S, T[S]] : never

然而有个用例报错了,原来是用例里涉及到了可选属性,那么我们可以用 Required 把它的可选去掉。不过需要注意,如果本身的是可选属性,且类型显式地设为 undefined, Required 会将它转为 never:

type TestUndefined = Required<{ key?: undefined }> // { key: never }

那么写个工具类型将 never 转为 undefined 就行了:

type NeverToUndefined<T> = [T] extends [never] ? undefined : T

最终代码为:

type NeverToUndefined<T> = [T] extends [never] ? undefined : T
type ObjectEntries<T, S extends keyof T = keyof T> = S extends S ? [S, NeverToUndefined<Required<T>[S]>] : never