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]]
, 不过要限制一下 S
为 keyof 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