【Typescript 系列】类型体操之中等篇题型(第十四节)解读

115 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情

1. 引言

接着上一节中,接下来我们继续Ts中等篇的题型练习 https://github.com/type-challenges/type-challenges/blob/main/README.zh-CN.md 提供的TypeScript 类型体操姿势合集题型,目的是为了让大家更好的了解TS的类型系统,编写自己的类型工具,或者单纯的享受挑战的乐趣!

2. 题型

  1. ObjectEntries:实现Object.entries的类型版本
interface Model {
  name: string;
  age: number;
  locations: string[] | null;
}
type modelEntries = ObjectEntries<Model> // ['name', string] | ['age', number] | ['locations', string[] | null];

思路:    首先先分析需要返回一个元祖值,联想到可以使用映射类型映射对象的每一个键值对,在映射过程中使用对应键获得对应键值,在这个重新的映射过程中我们可以使用类型组合返回我们要的元祖类型;①在映射键过程中去除可选属性;②键值为元组类型,第一个值为键,键值则需要判断是否为undefined;如果是则返回,我们使用[T] extends [undefined] 即可判断是否传入类型为undefined;如果不是单纯的undefined类型则需要使用Exclude 过滤掉传入类型中的undefined值; 解答:

type GetEntries<T> = [T] extends [undefined] ? undefined : Exclude<T, undefined>
type ObjectEntries<T> = {
  [P in keyof T]-?:[P, GetEntries<T[P]>]
}[keyof T]
type Demo = ObjectEntries<Model> // type Demo = ["name", string] | ["age", number] | ["locations", string[] | null
type Demo2 = ObjectEntries<Partial<Model>> // type Demo2 = ["name", string] | ["age", number] | ["locations", string[] | null]
type Demo3 = ObjectEntries<{ key?: undefined }> // type Demo3 = ["key", undefined]
type Demo4 = ObjectEntries<{ key: undefined }> // type Demo4 = ["key", undefined]

  1. Shift:实现Array.shift的类型版本(返回去除数组头部元素的原数组)
type Result = Shift<[3, 2, 1]> // [2, 1]

思路:    使用extends子类型判断在加上infer 关键字推导即可; 解答:

type Shift<T> = T extends [infer M,...infer N] ? [...N] : undefined
type Demo = Shift<[3, 2, 1]> // type Demo = [2, 1]
type Demo2 = Shift<['a', 'b', 'c', 'd']> // type Demo2 = ["b", "c", "d"]

  1. Tuple to Nested Object:给定一个只包含字符串类型的元组T和一个类型U,递归地构建一个对象。
type a = TupleToNestedObject<['a'], string> // {a: string}
type b = TupleToNestedObject<['a', 'b'], number> // {a: {b: number}}
type c = TupleToNestedObject<[], boolean> // boolean. if the tuple is empty, just return the U type

思路:    首先,根据题意我们要先过滤空元组;接下来开始嵌套元组类型使用递归方法,首先使用infer关键字分离需要获取到当前元组需要设置的键,接着在使用映射类型操作设置新的类型;键需要使用断言非空,键值使用递归操作嵌套即可。 解答:

须知:
type TargetTuple<T> = {[P in keyof T]: T[P]}
type TargetTupleIndex<T> = {[P in keyof T]: P}
type Text1 = TargetTuple<['a', 'b', 'c']> // type Text1 = ["a", "b", "c"]
type Text2 = TargetTupleIndex<['a', 'b', 'c']> // type Text2 = ["0", "1", "2"]
// 解题:
type TupleToNestedObject<T extends unknown[], U> = T extends [] ? 
U : T extends [infer M, ...infer N] ? 
{
  [P in M as M extends PropertyKey ? M : never]: TupleToNestedObject<N, U>
} : undefined

type Demo = TupleToNestedObject<['a'], string> // type Demo = {a: string;}
type Demo2 = TupleToNestedObject<['a', 'b'], number> // type Demo2 = {a: {b: number;};}
type Demo3 = TupleToNestedObject<['a', 'b', 'c'], boolean> // type Demo3 = {a: {b: {: boolean;};;}}
type Demo4 = TupleToNestedObject<[], boolean> // type Demo4 = boolean