来做做这 48 道 TypeScript 练习题,试试你的 TS 学得怎么样了!(附答案解析)

您所在的位置:网站首页 ts的网站推荐 来做做这 48 道 TypeScript 练习题,试试你的 TS 学得怎么样了!(附答案解析)

来做做这 48 道 TypeScript 练习题,试试你的 TS 学得怎么样了!(附答案解析)

2023-09-16 03:47| 来源: 网络整理| 查看: 265

下面所有题目来源于阿宝哥的 awesome-typescript 开源项目,可以点个star😁。

🍔前言

根据以下题目解题中你可以学习了解到并应用的知识点有:

泛型应用

联合类型、交叉类型使用

函数重载

元组

extends分布式条件类型、约束

in关键字

as断言

keyof关键字

infer关键字

-?操作符

-readonly 删除只读符号

循环遍历中属性值类型为never会被省略

[number]获取所有数组类型索引值

与any交叉类型时的情况

什么是Flasy类型

协变逆变

TypeScript 内置工具类型的使用

Omit

Pick

Required

Extract

Exclude

Parameters

ReturnType

下面工具类型实现以及该题实现的逻辑分析觉得错误,或书写有误,希望大佬帮忙指正😀,有更好的实现方式和不明白的地方欢迎评论区留言。

第一题

以下代码为什么会提示错误,应该如何解决下述问题。

type User = { id: number; kind: string; }; function makeCustomer(u: T): T { // Error(TS 编译器版本:v4.4.2) // Type '{ id: number; kind: string; }' is not assignable to type 'T'. // '{ id: number; kind: string; }' is assignable to the constraint of type 'T', // but 'T' could be instantiated with a different subtype of constraint 'User'. return { id: u.id, kind: 'customer' } }

上面代码出现错误原因:泛型 T 只是约束于User类型,并不是局限于 User类型,所以返回结果 应该还需要接收其他类型变量。

解决办法:

第一种,T 类型兼容 User 类型

function makeCustomer(u: T): T { // Error(TS 编译器版本:v4.4.2) // Type '{ id: number; kind: string; }' is not assignable to type 'T'. // '{ id: number; kind: string; }' is assignable to the constraint of type 'T', // but 'T' could be instantiated with a different subtype of constraint 'User'. return { ...u, id: u.id, kind: 'customer', }; }

第二种,返回值类型修改为 User类型

function makeCustomer(u: T): User { // Error(TS 编译器版本:v4.4.2) // Type '{ id: number; kind: string; }' is not assignable to type 'T'. // '{ id: number; kind: string; }' is assignable to the constraint of type 'T', // but 'T' could be instantiated with a different subtype of constraint 'User'. return { id: u.id, kind: 'customer', }; } function makeCustomer(u: T): ReturnMake { // Error(TS 编译器版本:v4.4.2) // Type '{ id: number; kind: string; }' is not assignable to type 'T'. // '{ id: number; kind: string; }' is assignable to the constraint of type 'T', // but 'T' could be instantiated with a different subtype of constraint 'User'. return { id: u.id, kind: 'customer', }; } type ReturnMake = { [K in keyof U as K extends keyof T ? K : never]: U[K]; }; makeCustomer({ id: 18584132, kind: '888', price: 99 });

1、ReturnMake工具类型,接收 T,U 两个泛型, T 约束于 User,

2、遍历 User 中的 key ,并使用 as 断言,如果K(也就是 User 类型的 key),约束于 泛型类型的 key 就返回 K,否侧返回 never,U[K] 取键值。

第二题

本道题我们希望参数 a 和 b 的类型都是一致的,即 a 和 b 同时为 number 或 string 类型。当它们的类型不一致的值,TS 类型检查器能自动提示对应的错误信息。

条件function f(a: string | number, b: string | number) { if (typeof a === 'string') { return a + ':' + b; // no error but b can be number! } else { return a + b; // error as b can be number | string } }

解决办法:函数重载

function f(a: string, b: string): string; function f(a: number, b: number): number; function f(a: string | number, b: string | number) { if (typeof a === 'string' || typeof b === 'string') { return a + ':' + b; } else { return a + b; } } f(2, 3); // Ok f(1, 'a'); // Error f('a', 2); // Error f('a', 'b'); // Ok

使用函数重载当调用函数时,会依次匹配定义f函数类型,内部,使用 typeof 判断 a 和 b 的类型对应逻辑。

第三题

如何定义一个 SetOptional 工具类型,支持把给定的keys对应的属性变成可选的?对应的使用示例如下所示:

type Foo = { a: number; b?: string; c: boolean; } // 测试用例 type SomeOptional = SetOptional; // type SomeOptional = { // a?: number; // 该属性已变成可选的 // b?: string; // 保持不变 // c: boolean; // }

SetOptional工具类型实现:

// 第一种 Omit + Partial + Pick type SetOptional = Omit & Partial; // 第二种 type SetOptionalOmit = Pick & Partial;

SetOptional工具类型:接收两个泛型 ,T为目标类型,K 为指定的 keys,K 需要 约束于T 类型的 keys,Omit & Partial:

/** * Omit 为反选的意思, * 以上面测试用例讲解 * 1.首先 Omit => Omit => { c: boolean } * Partial 这里是嵌套,我们先看看 Pick * 2.Pick => Pick => { a: number, b?: string } => * 3.Partial 所有变成可选 => { a?: number, b?: string } * 4.最后我们得到:{ c: boolean } & { a?: number, b?: string } * 5.合并得到之后: { a?: number, b?: string , c: boolean} */

第二种SetOptionalOmit的意思 和第一种差不多理解为上面的步骤是:2 -> 1 -> 3 -> 4 -> 5。

在实现SetOptional工具类型之后,感兴趣还可以实现一个 SetRequired工具类型,把给指定的 keys 对应属性变成必填。

实现SetRequired工具方法:

// 第一种 type SetRequired = Omit & Required; // 第二种 type SetRequiredOmit = Pick & Required;

Required工具类型,就是把所有接口类型属性变成必选。

第四题

Pick的作用是将某个类型中的子属性挑出来,变成包含这个类型部分属性的子类型。

interface Todo { title: string; description: string; completed: boolean; } type TodoPreview = Pick; const todo: TodoPreview = { title: "Clean room", completed: false };

那么如何定义一个 ConditionalPick工具类型,支持根据指定的 Condition 条件来生成新的类型,对应的使用示例如下:

interface Example { a: string; b: string | number; c: () => void; d: {}; } // 测试用例: type StringKeysOnly = ConditionalPick; //=> {a: string}

实现ConditionalPick工具类型方法:

type ConditionalPick = { [K in keyof V as V[K] extends T ? K : never]: V[K]; };

1、in keyof遍历 V 泛型;

2、通过类型断言判断 V[K] 对应键值是否约束于传入的 string如果是 true 那么断言成返回遍历的当前 K,否则为 never。

返回 never 在 TypeScript 编译器中,会默认认为这是个用不存在的类型,也相当于没有这个 K 会被过滤,对应值则是 V[K] 获取。

像TypeScript 内部工具实现工具方法 Extract、Exclude 也是通过返回never来排除。

type Extract = T extends U ? T : never; type Exclude = T extends U ? never : T; 第五题

定义一个工具类型 AppendArgument,为已有的函数类型增加指定类型的参数,新增的参数名是x,将作为新函数类型的第一个参数。具体的使用示例如下所示:

type Fn = (a: number, b: string) => number type AppendArgument = // 你的实现代码 type FinalFn = AppendArgument // (x: boolean, a: number, b: string) => number 使用 Parameters+ ReturnType工具类型实现: type Fn = (a: number, b: string) => number type AppendArgument any, T> = ( x: T, ...args: Parameters ) => ReturnType; type FinalFn = AppendArgument; // type FinalFn = (x: string, a: number, b: string) => number

AppendArgument工具类型中

1、泛型 F 为需要增加参数x 的函数类型,F 约束于函数类型,泛型T为x参数指定的类型,返回一个新函数类型,

2、x参数类型为 T,...args剩余参数类型使用Parameters工具类型拿到F泛型的数组类型参数类型,ReturnType工具类型拿到F函数类型的返回类型。

使用infer方式 type AppendArgument any, T> = F extends ( ...args: infer P ) => infer Return ? (x: T, ...args: P) => Return : never; type FinalFn = AppendArgument; // type FinalFn = (x: boolean, a: number, b: string) => number

infer推导拿到参数类型P返回值类型为Return,再从新返回一个新函数x参数为T,...args参数类型为前面推导保留的P,返回值即Return。

第六题

定义一个NativeFlat工具类型,支持把数组类型拍平(扁平化)。具体的使用示例如下所示:

type NaiveFlat = // 你的实现代码 // 测试用例: type NaiveResult = NaiveFlat // NaiveResult的结果: "a" | "b" | "c" | "d"

使用递归写法:

type NaiveFlat = T extends (infer P)[] ? P extends any[] ? NaiveFlat

: P : never; type NaiveResult = NaiveFlat; // type NaiveResult = "a" | "b" | "c" | "d"

上面NaiveFlat实现方式

1、首先需要在约束条件中使用infer关键字推导出 T 传入的数组类型,并用 P 保存数组类型。

2、三元嵌套判断P类型是否约束于类型any[]如果还是是数组继续递归遍历调用NaiveFlat

并传入P,放 P类型不满足 any[],返回最后的扁平完成P类型所以得到最终联合类型"a" | "b" | "c" | "d" 。

个人步骤流程理解:

以传入[['a'], [['b', 'c']], ['d']]值为例

1、第一次得到的P被推断出的类型为 ["a"] | [["b", "c"]] | ["d"],满足约束;

2、走到P是否约束any[],此时还满足还存在数组情况, 因此继续递归传入 P;

3、第二次infer P推导出P的类型为 "a" | ["b", "c"] | "d",再次约束,此时在extends条件语句中,联合类型为裸类型时,会被分发,先走'a'逻辑,不满足与any[]返回'a';

4、走完'a'就走到['b', 'c'],即满足any[]继续递归,返回得到 =>'a' | 'b' | 'c';

5、最后走'd',最终得到 => 'a' | 'b' | 'c' | 'd'。

另外如果是固定二维数组的话,可以试试这样:

type NaiveFlat = T[number][number]; const testArr = [['a'], ['b', 'c'], ['d']]; const testArrType = typeof testArr; // string[][] type NaiveResult = NaiveFlat; // type NaiveResult = "a" | "b" | "c" | "d" [number] 取数组的中值作为 key, number 是数组下标 ["a"] | ["b", "c"] | ["d"]

T[number][number]可以理解为 => T[][]一个二维数组的类型表达式,类型[number]在 TypeScript 中,可以代表取数组的中值作为 key, number是数组下标。因此T[number]对应着["a"] | ["b", "c"] | ["d"],T[number][number]则为 "a" | "b" | "c" | "d"。

第七题

使用类型别名定义一个EmptyObject类型,使得该类型只允许空对象赋值:

type EmptyObject = {} // 测试用例 const shouldPass: EmptyObject = {}; // 可以正常赋值 const shouldFail: EmptyObject = { // 将出现编译错误 prop: "TS" }

EmptyObject工具类型实现:

type EmptyObject = { [K in keyof any]: never; }; // 测试用例 const shouldPass: EmptyObject = {}; // 可以正常赋值 const shouldFail: EmptyObject = { // 将出现编译错误 prop: "TS" }

EmptyObject类型中[K in keyof any] 等同于[K in string | number | symbol],将所有对象属性对应类型设置为never。

注意的是对象的索引类型是string | number | symbol。

在通过 EmptyObject类型的测试用例检测后, 我们来更改以下 takeSomeTypeOnly函数的类型定义 让它的参数只允许严格SomeType类型的值。具体的使用示例如下所示:

type SomeType = { prop: string } // 更改以下函数的类型定义,让它的参数只允许严格SomeType类型的值 function takeSomeTypeOnly(x: SomeType) { return x } // 测试用例: const x = { prop: 'a' }; takeSomeTypeOnly(x) // 可以正常调用 const y = { prop: 'a', addditionalProp: 'x' }; takeSomeTypeOnly(y) // 将出现编译错误

具体实现:

type Exclusive = { [K in keyof T1]: K extends keyof T2 ? T1[K] : never; }; function takeSomeTypeOnly(x: Exclusive) { return x; } takeSomeTypeOnly({ prop: 'a' }); // OK takeSomeTypeOnly({ prop: 'a', addditionalProp: 'x' }) // 将出现编译错误

遍历SomeType类型,只留下SomeType类型与传入的参数类型T中共有的属性,共有的属性类型拿的是SomeType对应的属性类型。不共有的设置为never排除,也就是将prop之外的其他属性气去除。

第八题

定义NonEmptyArray工具类型,用于确保数据非空数组。

type NonEmptyArray = // 你的实现代码 const a: NonEmptyArray = [] // 将出现编译错误 const b: NonEmptyArray = ['Hello TS'] // 非空数据,正常使用

NonEmptyArray工具类型实现:

type NonEmptyArray = [T, ...T[]]; const a: NonEmptyArray = [] // Error const b: NonEmptyArray = ['Hello TS'] // OK

[T, ...T[]]确保第一项一定是T,[...T[]],为剩余数组类型。

第九题

定义一个JoinStrArray工具类型,用于根据指定的Separator分隔符,对字符串数组类型进行拼接。具体的使用示例如下所示:

type JoinStrArray = // 你的实现代码 // 测试用例 type Names = ["Sem", "Lolo", "Kaquko"] type NamesComma = JoinStrArray // "Sem,Lolo,Kaquko" type NamesSpace = JoinStrArray // "Sem Lolo Kaquko" type NamesStars = JoinStrArray // "Sem⭐️Lolo⭐️Kaquko"

JoinStrArray工具类型实现:

type JoinStrArray< Arr extends string[], Separator extends string > = Arr extends [infer A, ...infer B] ? `${A extends string ? A : ''}${B extends [string, ...string[]] ? `${Separator}${JoinStrArray}` : ''}` : '';

JoinStrArray工具方法,Arr泛型必须约束于string[]类型,Separator为分隔符,也必须约束于string类型;

1、首先Arr约束于后面[infer A, ...infer B]并通过infer关键字推导拿到第一个索引A的类型,以及剩余(rest)数组的类型为B;

2、如果满足约束,则连接字符,连接字符使用模板变量,先判断A(也就是第一个索引)是否约束于string类型,满足就取第一个A否则直接返回空字符串;

3、后面连接的B(...rest)判断是否满足于[string, ...string[]],意思就是是不是还有多个索引。如果有,用分割符号,加上递归再调用JoinStrArray工具类型方法,Arr泛型就再为 B ,分隔符泛型Separator不变。减治思想,拿出数组的每一项,直至数组为空。

最开始的话,如果Arr不满足约束,那么直接返回为空字符串。

第十题

实现一个Trim工具类型,用于对字符串字面量类型进行去空格处理。具体的使用示例如下所示:

type Trim = // 你的实现代码 // 测试用例 Trim //=> 'semlinker'

Trim工具类型实现:

type TrimLeft = V extends ` ${infer R}` ? TrimLeft : V; type TrimRight = V extends `${infer R} ` ? TrimRight : V; type Trim = TrimLeft; // 测试用例 type Result = Trim //=> 'semlinker'

利用ts模板字符串,配合infer去除空格。

需要定义两个工具类型方法,Trim分解成TrimLeft 和 TrimRight,一个是去除左边空格的,另一个去除右边。

去除空格主要通过extends配合infer在模板字符串中使用,并且,如果去除左边空格,需要在左边添加一个空格( ${infer R}**),**之后就是映射类型可以递归。

第十一题

实现一个IsEqual工具类型,用于比较两个类型是否相等。具体的使用示例如下所示:

type IsEqual = // 你的实现代码 // 测试用例 type E0 = IsEqual; // false type E1 = IsEqual // true type E2 = IsEqual; // false

IsEqual工具类型实现:

type IsEqual = [A] extends [B] ? [B] extends [A] ? true : false : false

这里需要考虑never类型和联合类型,所以用到元组进行处理比较。

IsEqual工具类型,如果[A]约束于[B]且[B]也满足约束于[A]说明他们相等,否则不相等。

第十二题

实现一个Head工具类型,用于获取数组类型的第一个类型。具体的使用示例如下所示:

type Head = // 你的实现代码 // 测试用例 type H0 = Head // never type H1 = Head // 1 type H2 = Head // 3

Head工具类型实现:

type Head1 = T extends [infer H, ...T[]] ? H : never; // 测试用例 type H0 = Head // never type H1 = Head // 1 type H2 = Head // 3 type H3 = Head // "a" type H4 = Head // undefined type H5 = Head // null

通过infer关键字推导取出数组第一项的类型,H保存该类型,如果泛型T满足约束,返回推导的第一项类型H,否则never,...T[]取出剩余数组。

第十三题

实现一个Tail工具类型,用于获取数组类型除了第一个类型外,剩余的类型。具体的使用示例如下所示:

type Tail = // 你的实现代码 // 测试用例 type T0 = Tail // [] type T1 = Tail // [2] type T2 = Tail // [2, 3, 4, 5]

Tail工具类型实现:

type Tail1 = T extends [infer H, ...infer R] ? R : never; // 测试用例 type T0 = Tail; // [] type T1 = Tail; // [2] type T2 = Tail; // [2, 3, 4, 5]

实现方式与第十二题类似。

第十四题

实现一个Unshift工具类型,用于把指定类型E作为第一个元素添加到 T 数组类型中。具体的使用示例如下所示:

type Unshift = // 你的实现代码 // 测试用例 type Arr0 = Unshift; // [1] type Arr1 = Unshift; // [0, 1, 2, 3]

Unshift实现方法:

type Unshift = [E, ...T]; // 测试用例 type Arr0 = Unshift; // [1] type Arr1 = Unshift; // [0, 1, 2, 3]

新建一个数组,第一项类型为E,剩余使用...T连接。

第十五题

实现一个Shift工具类型,用于移除T数组类型中的第一个类型。具体的使用示例如下所示:

type Shift = // 你的实现代码 // 测试用例 type S0 = Shift type S1 = Shift

Shift工具类型实现:

type Shift = T extends [infer A, ...infer B] ? B : []; // 测试用例 type S0 = Shift; // [2, 3] type S1 = Shift; // [number, boolean] type S2 = Shift; // []

...infer B去除第一项之后的集合,使用变量B保存该类型。如果满足约束,返回剩余参数类型,也就是B。

第十六题

实现一个Push工具类型,用于把指定类型E作为第最后一个元素添加到T数组类型中。具体的使用示例如下所示:

type Push = // 你的实现代码 // 测试用例 type Arr0 = Push // [1] type Arr1 = Push // [1, 2, 3, 4]

Push实现:

type Push = [...T, V]; // 你的实现代码 // 测试用例 type Arr0 = Push // [1] type Arr1 = Push // [1, 2, 3, 4]

Push工具类型的实现与第十四题Unshift实现类似。

第十七题

实现一个Includes工具类型,用于判断指定的类型E是否包含在T数组类型中。具体的使用示例如下所示:

type Includes = // 你的实现代码 type I0 = Includes // false type I1 = Includes // true type I2 = Includes // true

Includes工具类型实现:

type Includes = U extends T[number] ? true : false; // 测试用例 type I0 = Includes // false type I1 = Includes // true type I2 = Includes // true

这里T[number]可以理解返回T数组元素的类型,比如传入的泛型T为[2, 2, 3, 1],那么T[number]被解析为:2 | 2 | 3 | 1。

第十八题

实现一个UnionToIntersection工具类型,用于把联合类型转换为交叉类型。具体的使用示例如下所示:

type UnionToIntersection = // 你的实现代码 // 测试用例 type U0 = UnionToIntersection // never type U1 = UnionToIntersection // { name: string; } & { age: number; }

UnionToIntersection工具类型实现:

export type UnionToIntersection = ( Union extends unknown ? (distributedUnion: Union) => void : never ) extends (mergedIntersection: infer Intersection) => void ? Intersection : never; // 测试用例 type U0 = UnionToIntersection // never type U1 = UnionToIntersection // { name: string; } & { age: number; }

1、extends unknown始终为true,默认进入到分发情况

2、会声明一个以Union为入参类型的函数类型A,即(distributedUnion: Union) => void,该函数约束于以mergedIntersection类型为入参的函数类型B,即(mergedIntersection: infer Intersection) => void。

3、如果函数A能继承函数B则 返回infer Intersection声明的Intersection,否则返回never,再利用函数参数类型逆变,从而实现得到的结果从联合类型到交叉类型的转变。

这里是也设计到一个知识点:**分布式条件类型,**条件类型的特性:分布式条件类型。在结合联合类型使用时(只针对extends左边的联合类型),分布式条件类型会被自动分发成联合类型。例如,T extends U ? X : Y,T的类型为A | B | C,会被解析为(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)。

都知道infer声明都是只能出现在extends子语句中。但是,在协变的位置上,同一类型变量的多个候选类型会被推断为联合类型:

type Foo = T extends { a: infer U, b: infer U } ? U : never; type T10 = Foo; // string type T11 = Foo; // string | number

在逆变的位置上,同一个类型多个候选类型会被推断为交叉类型:

type Bar = T extends { a: (x: infer U) => void, b: (x: infer U) => void } ? U : never; type T20 = Bar void, b: (x: string) => void }>; // string type T21 = Bar void, b: (x: number) => void }>; // string & number

相关链接:

有条件类型中的类型推断

关于协变和逆变

第十九题

实现一个OptionalKeys工具类型,用来获取对象类型中声明的可选属性。具体的使用示例如下所示:

type Person = { id: string; name: string; age: number; from?: string; speak?: string; }; type OptionalKeys = // 你的实现代码 type PersonOptionalKeys = OptionalKeys // "from" | "speak"

OptionalKeys工具类型实现:

type OptionalKeys = { [P in keyof T]-?: undefined extends T[P] ? P : never; }[keyof T]; // 测试用例 type PersonOptionalKeys = OptionalKeys; // "from" | "speak"

例如Peson类型:

1、首先会遍历所有Person属性,-?字符的作用是,再完成边extends判断后将T中所有的属性都变成必须属性,为防止属性值类型undefined;

2、右边,判断undefined是否约束于当前键值,如果满足约束当前属性的类型为键名。

在 TypeScript 中,如果添加了可选属性,会被隐式添加一个 undefined类型,比如from?其实是string | undefined

3、{ ... }[keyof T]取键值,因为id,age,name的属性类型都为never,取值的时候会被忽略掉,因为never是一个用不存在的类型,因此就只剩下from、speak属性的值了就是 "from" | "speak"组成联合类型返回。

第二十题

实现一个Curry工具类型,用来实现函数类型的柯里化处理。具体的使用示例如下所示:

type Curry< F extends (...args: any[]) => any, P extends any[] = Parameters, R = ReturnType > = // 你的实现代码 type F0 = Curry Date>; // () => Date type F1 = Curry Date>; // (arg: number) => Date type F2 = Curry Date>; // (arg_0: number) => (b: string) => Date

Curry工具类型实现:

type Curry< F extends (...args: any[]) => any, P extends any[] = Parameters, R = ReturnType > = P extends [infer A, ...infer B] ? B extends [] ? (arg: A) => R : (arg: A) => Curry R> : () => R; // 测试用例 type F0 = Curry Date>; // () => Date type F1 = Curry Date>; // (arg: number) => Date type F2 = Curry Date>; // (arg_0: number) => (b: string) => Date

F为需要柯里化的函数类型;

P通过Paramters获取F参数集合;

R通过ReturnType获取F函数类型返回值;

逻辑分析:

1、需要先拿到args数组的第一项和剩余参数集合,[infer A, ...infer B];

2、使用 extends 判断P是否满足于[infer A, ...infer B],不满足直接返回() => R,说明没有参数;

3、如果有一个或者多个参数,这里则继续递归;

4、首先...infer B需要判断是否约束与[]来做终止条件;

5、满足约束直接返回(args: A) => R;

6、否则递归,创建一个函数,并且参数类型为A,返回值则为Curry R>,新函数入参B,为剩余参数类型集合,它的返回值确保最后一个返回因此保留为R即(arg: A) => Curry R>。

第二十一题

实现一个Merge工具类型,用于把两个类型合并成一个新的类型。第二种类型(SecondType)的Keys将会覆盖第一种类型(FirstType)的Keys。具体的使用示例如下所示:

type Foo = { a: number; b: string; }; type Bar = { b: number; }; type Merge = // 你的实现代码 const ab: Merge = { a: 1, b: 2 };

Merge工具类型实现:

interface Foo { b: number } interface Bar { a: number; b: string } type Merge = { [K in keyof (FirstType & SecondType)]: K extends keyof SecondType ? SecondType[K] : K extends keyof FirstType ? FirstType[K] : never; }; // 测试用例 type Obj = Merge // { a: number ; b: string }

注意的是:合并属性,后一个类型会覆盖前一个类型。

逻辑分析:

1、将FirstType和SecondType做交叉类型,并遍历他们的每一个属性;

2、如果当前的属性名在SecondType类型中,则使用SecondType类型中的当前属性值;

3、如果当前属性名在FirstType类型中,则使用First类型中的当前属性值;

4、否则为never。

其他解法:

结合Omit内置工具类型

type Merge = Omit & SecondType; type Obj = Merge // { a: number ; b: string } const ab: Obj = { a: 1, b: "1" };

1、先将FirstType类型中已有的和SecondType类型中相同属性删除

2、将前面结果和SecondType做交叉类型,得到合并后的结果。

第二十二题

实现一个RequireAtLeastOne工具类型,它将创建至少含有一个给定Keys的类型,其余的Keys保持原样。具体的使用示例如下所示:

type Responder = { text?: () => string; json?: () => string; secure?: boolean; }; type RequireAtLeastOne< ObjectType, KeysType extends keyof ObjectType = keyof ObjectType, > = // 你的实现代码 // 表示当前类型至少包含 'text' 或 'json' 键 const responder: RequireAtLeastOne = { json: () => '{"message": "ok"}', secure: true };

RequireAtLeastOne工具类型实现:

type RequireAtLeastOne< ObjectType, KeysType extends keyof ObjectType = keyof ObjectType > = KeysType extends keyof ObjectType ? ObjectType & { [K in KeysType]-?: ObjectType[K] } : never; // 表示当前类型至少包含 'text' 或 'json' 键 const responder: RequireAtLeastOne = { json: () => '{"message": "ok"}', secure: true }; // @ts-expect-error 因为没有'text'和'json'中的任何一个,报错 const responder2: RequireAtLeastOne = { secure: true };

1、给定的Keys类型需要约束于ObjectType;

2、如果给定的KeysType中的Keys在ObjectType类型里,创建一个新的类型,遍历KeysType作为Key,并且-?字符,将可选变为必选,值类型为ObjectType[K],然后将ObjectType和这个创建的新的类型做交叉类型。联合类型在extends条件中会做分发,因此最后组成联合类型返回;

3、否则返回never。

第二十三题

实现一个RemoveIndexSignature工具类型,用于移除已有类型中的索引签名。具体的使用示例如下所示:

interface Foo { [key: string]: any; [key: number]: any; bar(): void; } type RemoveIndexSignature = // 你的实现代码 type FooWithOnlyBar = RemoveIndexSignature; //{ bar: () => void; }

RemoveIndexSignature工具类型实现:

type RemoveIndexSignature = { [K in keyof T as string extends K ? never : number extends K ? never : K]: T[K]; }; // 测试用例 type FooWithOnlyBar = RemoveIndexSignature; //{ bar: () => void; }

1、遍历T,利用as断言实现对K的判断过滤;

2、当前的key如果满足string | number直接返回never过滤当前属性;

3、否则拿当前K,当前K值类型为T[K]。

第二十四题

实现一个Mutable工具类型,用于移除对象类型上所有属性或部分属性的readonly修饰符。具体的使用示例如下所示:

type Foo = { readonly a: number; readonly b: string; readonly c: boolean; }; type Mutable = // 你的实现代码 const mutableFoo: Mutable = { a: 1, b: '2', c: true }; mutableFoo.a = 3; // OK mutableFoo.b = '6'; // Cannot assign to 'b' because it is a read-only property.

Mutale工具类型实现:

type Mutable = { -readonly [K in Keys]: T[K]; } & Omit; const mutableFoo: Mutable = { a: 1, b: '2', c: true }; // 测试用例 mutableFoo.a = 3; // OK mutableFoo.b = '6'; // Cannot assign to 'b' because it is a read-only property.

1、遍历Keys,-readonly删除只读符号;

2、Omit返回删除在T中的Keys属性的新类型,最后与前面的结果组成交叉类型。

第二十五题

实现一个IsUnion工具类型,判断指定的类型是否为联合类型。具体的使用示例如下所示:

type IsUnion = // 你的实现代码 type I0 = IsUnion; // true type I1 = IsUnion; // false type I2 = IsUnion; // false

IsUnion工具类型实现方法:

type IsUnion = T extends any ? ([U] extends [T] ? false : true) : never; // 你的实现代码 // 测试用例 type I0 = IsUnion; // true type I1 = IsUnion; // false type I2 = IsUnion; // false

1、第一步T extends any确保始终为真,联合类型做分发;

2、联合类型T写成[T]就变成了普通类型,extends的时候不会分发执行,利用其分发的特性,后面的[T]就是一个联合类型拆开后的某一个,

3、因此如果是联合类型的话[U] extends [T]一定为否。

比如传入string | nuber类型会

// 首先会被分发得到 type IsUnion = string extends ? any ? ([string | number]) extends [string] ? false : true) : never | number extends ? ([string | number]) extends [string] ? false :true) : never // => true | true => true string | never 会被简化为 never string | unknown => unknown 第二十六题

实现一个IsNever工具类型,判断指定的类型是否为never类型。具体的使用示例如下所示:

type IsNever = // 你的实现代码 type I0 = IsNever // true type I1 = IsNever // false type I2 = IsNever // false

IsNever工具类型实现:

type IsNever = [T] extends [never] ? true : false; // 测试用例 type II0 = IsNever // true type II1 = IsNever // false type II2 = IsNever // false type II3 = IsNever // false type II4 = IsNever // false type II5 = IsNever // false

1、[T]和[never]为元组,作为包装类型,联合类型不会被分发;

2、never类型不能扩展never类型,但是never[]可以扩展never[]。

第二十七题

实现一个Reverse工具类型,用于对元组类型中元素的位置颠倒,并返回该数组。元组的第一个元素会变成最后一个,最后一个元素变成第一个。

type Reverse< T extends Array, R extends Array = [] > = // 你的实现代码 type R0 = Reverse // [] type R1 = Reverse // [3, 2, 1]

Reverse工具类型实现:

type Reverse = T extends [infer First, ...infer Rest] ? [...Reverse, First] : []; // 测试用例 type R0 = Reverse // [] type R1 = Reverse // [3, 2, 1] type R2 = Reverse // [5, 4, 3, 2, 1]

采用递归方式,每次递归都把第一项First放在最后,并把递归结果展开。

第二十八题

实现一个Split工具类型,根据给定的分隔符(Delimiter)对包含分隔符的字符串进行切割。可用于定义 String.prototype.split方法的返回值类型。具体的使用示例如下所示:

type Item = 'semlinker,lolo,kakuqo'; type Split< S extends string, Delimiter extends string, > = // 你的实现代码 type ElementType = Split; // ["semlinker", "lolo", "kakuqo"]

Split工具类型实现:

type Item = 'semlinker,lolo,kakuqo'; export type Split< S extends string, Delimiter extends string > = S extends `${infer Head}${Delimiter}${infer Tail}` ? [Head, ...Split] : S extends Delimiter ? [] : [S]; // 测试用例 type ElementType = Split; // ["semlinker", "lolo", "kakuqo"] type ElementType2 = Split; // ["a", "b", "c", "", "d"] type ElementType3 = Split; // ["a", "b", "c", "d", "e", "f"]

1、${infer Head}${Delimiter}${infer Tail}映射类型在模板变量中使用,将一个字符串做拆解;

2、第一步会变成${infer "semlinker"}${,}${infer "lolo,kakuqo"},减治思想,再递归依次取第二位,直至递归到Delimiter符号的最后一项,S extends Delimiter处理Delimiter为空格的情况。

第二十九题

实现一个ToPath工具类型,用于把属性访问(.或 [])路径转换为元组的形式。具体的使用示例如下所示:

type ToPath = // 你的实现代码 ToPath //=> ['foo', 'bar', 'baz'] ToPath //=> ['foo', '0', 'bar', 'baz']

ToPath工具类型实现:

//以 . 拆分,处理每一项 type IndexSignature = T extends `${infer H}[${infer M}][${infer R}]` ? [H, M, ...IndexSignature] : T extends `${infer F}[${infer L}]` ? [F, L] : [T]; // 验证数组是否有 '' type NonSpace = T extends [infer H, ...infer R] ? R extends string[] ? H extends '' ? [...NonSpace] : [H, ...NonSpace] : never : T; // NonSpace 和 IndexSignature 组合起来 type ToPath = S extends `${infer H}.${infer R}` ? [...NonSpace, ...ToPath] : NonSpace; // 测试用例 type TT0 = ToPath //=> ['foo', 'bar', 'baz'] type TT1 = ToPath; // => ["foo", "0", "bar", "0", "1", "2", "3", "car"]

1、IndexSignature工具类型处理以.为拆分,并递归将每一项的子项放入到元组中;

2、IndexSignature处理比如完foo[0][1]会得到=>``["foo", "0", "", "1"];

3、NonSpace处理IndexSignature工具类型返回值数组中的空字符串;

4、ToPath以分隔符.拆分字符串,多项则拼接并递归,否则直接处理并返回。

第三十题

完善Chainable类型的定义,使得 TS 能成功推断出result变量的类型。调用option方法之后会不断扩展当前对象的类型,使得调用get方法后能获取正确的类型。

declare const config: Chainable type Chainable = { option(key: string, value: any): any get(): any } const result = config .option('age', 7) .option('name', 'lolo') .option('address', { value: 'XiaMen' }) .get() type ResultType = typeof result // 期望 ResultType 的类型是: // { // age: number // name: string // address: { // value: string // } // }

Chainable类型实现:

declare const config: Chainable; type Simplify = { [P in keyof T]: T[P]; }; type Chainable = { option( key: S, value: V ): Chainable< T & { [P in keyof { S: S } as `${S}`]: V; } >; get(): Simplify; }; const result = config .option('age', 7) .option('address', { name: 'Leslie' }) .get(); type ResultType = typeof result; // => { // age: number; // address: { // name: string; // }; // }

config可以进行链式调用,可以联想到 js 中return this这种思路,所以这里opiton的返回值就应该是一个新的Chainable,把添加的属性类型当做下一个Chainable的T。

1、Chainable类型定义中的option返回值类型为新的Chainable,将添加的属性当做下一个Chainable的T;

2、get类型直接就返回Chainable中的T。

第三十一题

实现一个Repeat工具类型,用于根据类型变量C的值,重复T类型并以元组的形式返回新的类型。具体的使用示例如下所示:

type Repeat = // 你的实现代码 type R0 = Repeat; // [] type R1 = Repeat; // [1] type R2 = Repeat; // [number, number]

Repeat工具类型实现:

type Repeat = A['length'] extends C ? A : Repeat; // 测试用例 type R0 = Repeat; // [] type R1 = Repeat; // [1] type R2 = Repeat; // [number, number]

1、A为接收根据C数量,重复T类型以元组形式返回的新类型;

2、判断A的数组长度是否满足C;

3、不满足则继续往里面添加需要重复的T类型;

4、否则返回添加完成的A类型结果。

第三十二题

实现一个RepeatString工具类型,用于根据类型变量C的值,重复T类型并以字符串的形式返回新的类型。具体的使用示例如下所示:

type RepeatString< T extends string, C extends number, > = // 你的实现代码 // 测试用例 type S0 = RepeatString; // '' type S1 = RepeatString; // 'aa' type S2 = RepeatString; // 'ababab'

RepeatString工具类型实现:

type RepeatString< T extends string, C extends number, A extends any[] = [], S extends string = '' > = A['length'] extends C ? A : RepeatString; // 测试用例 type RS0 = RepeatString; // '' type RS1 = RepeatString; // 'aa' type RS2 = RepeatString; // 'ababab'

与三十一题类似,多添加了一个返回最终的结果的S类型,A记录添加的数量。

第三十三题

实现一个ToNumber工具类型,用于实现把数值字符串类型转换为数值类型。具体的使用示例如下所示:

type ToNumber = // 你的实现代码 type T0 = ToNumber; // 0 type T1 = ToNumber; // 10 type T2 = ToNumber; // 20

ToNumber工具类型实现:

type ToNumber< T extends string, S extends any[] = [], L extends number = S['length'] > = `${L}` extends T ? L : ToNumber;

在 TypeScript 中没有直接的数字运算,但是可以通过数组长度转字符串再匹配需要字符串转换的字符串。

1、S类型为累加记录,L获取S的数组类型长度;

2、判断${L}是否满足约束T,不满足则,继续添加''空字符串,作为长度累加。

第三十四题

实现一个SmallerThan工具类型,用于比较数值类型的大小。具体的使用示例如下所示:

type SmallerThan< N extends number, M extends number, > = // 你的实现代码 // 测试用例 type S0 = SmallerThan; // true type S1 = SmallerThan; // false type S2 = SmallerThan; // true

SmallerThan工具类型实现:

type SmallerThan< N extends number, M extends number, A extends any[] = [] > = A['length'] extends M //=> M = 0 直接返回 false 1 => extends 2 ? false ? false : A['length'] extends N // => if M = 1,那么 N 应该就是 0, so M > N => 1 extends 1 true ? true : SmallerThan; // 否则 A length + 1 // 测试用例 type ST1 = SmallerThan // false type ST2 = SmallerThan2; // true

这里利用构造数组的长度来判断,默认是一个空数组进行递增,哪个先匹配上说明哪个小。

例如给N, M传入 0, 0:

1、默认从空数组进行递增,第一遍A['length']数组的长度为0,0 extends 0为true,也就是,当M为0,说明要么M===N,要么就N > M,因此返回**false**。

再例如给N, M传入1, 2:

1、第一遍我们直接跳过,会走到递归,第二遍得到A['length'] = 1;

2、第二遍:1 extends 2不约束说明M >= N,走到否则1 extends 1满足约束说明M > N,最后得到结果为**true**。

第三十五题

实现一个Add工具类型,用于实现对数值类型对应的数值进行加法运算。具体的使用示例如下所示:

type Add = // 你的实现代码 type A0 = Add; // 10 type A1 = Add // 28 type A2 = Add; // 40

Add工具类型实现:

type GenArr = S['length'] extends N ? S : GenArr; type Add = [ ...GenArr, ...GenArr ]['length']; // 测试用例 type Add1 = Add; // 3 type Add2 = Add; // 102

经过上面几道题洗礼,这到相加就很简单了,就是构建对应数值长度的数组。

GenArr工具类型通过数值构建对应长度数组。

第三十六题

实现一个Filter工具类型,用于根据类型变量F的值进行类型过滤。具体的使用示例如下所示:

type Filter = // 你的实现代码 type F0 = Filter; // [6, 7] type F1 = Filter; // ["kakuqo", "lolo"] type F2 = Filter; // [any, "abao"]

Filter工具类型实现:

type Filter = T extends [ infer A, ...infer B ] ? [A] extends [F] ? Filter : Filter : R; // 测试用例 type F0 = Filter; // [6, 7] type F1 = Filter; // ["kakuqo", "lolo"] type F2 = Filter; // [any, "abao"] type F3 = Filter; // [never, any, "abao"]

1、R为根据类型变量F的值进行类型过滤后的结果;

2、首先利用extends [infer A, ...infer B]来提取数组内的第一项,递归就能拿到全部;

3、之后判断类型的时候转换成元组类型[A] extends [F]能够避免联合类型在条件判断中分发执行的情况。

第三十七题

实现一个Flat工具类型,支持把数组类型拍平(扁平化)。具体的使用示例如下所示:

type Flat = // 你的实现代码 type F0 = Flat // [] type F1 = Flat // ["a", "b", "c"] type F2 = Flat // ["a", "b", "c", "d", "e", "f"]

Flat工具类型实现:

type Flat = T extends [infer First, ...infer Rest] ? First extends any[] ? [...Flat, ...Flat] : [First, ...Flat] : []; //测试用例 type F1 = Flat; // [1,2,3,4,5,6] type F2 = Flat // ["a", "b", "c", "d", "e", "f"]

1、[infer First, ...infer Rest]提取数组第一项

2、如果T为多项,第一项First判断是否还是数组,扁平多维数组情况,如果是,继续递归将Frist扁平,以及递归将Rest也扁平,否则First不是多为数组,递归Rest并扁平。

3、如果空数组,直接返回[]。

第三十八题

实现StartsWith工具类型,判断字符串字面量类型T是否以给定的字符串字面量类型U开头,并根据判断结果返回布尔值。具体的使用示例如下所示:

type StartsWith = // 你的实现代码 type S0 = StartsWith // true type S1 = StartsWith // false type S2 = StartsWith // false

此外,继续实现EndsWith工具类型,判断字符串字面量类型T是否以给定的字符串字面量类型U结尾,并根据判断结果返回布尔值。具体的使用示例如下所示:

type EndsWith = // 你的实现代码 type E0 = EndsWith // true type E1 = EndsWith // false type E2 = EndsWith // true

StartsWith工具类型实现:

type StartsWith< T extends string, U extends string > = T extends `${U}${infer Rest}` ? true : false; // 测试用例 type S0 = StartsWith; // true type S1 = StartsWith; // false type S2 = StartsWith; // false

${U}${infer Rest}将U放在开头,infer关键字,会自动推导匹配,如果推导的Rest变量类型满足约束则返回true否则返回false。

EndsWith工具类型实现:

type EndsWith = T extends `${infer Head}${U}` ? true : false; // 测试用例 type E0 = EndsWith; // true type E1 = EndsWith; // true type E2 = EndsWith; // true

${infer Head}${U}位置调换即可。与去除左边空格右边空格题目类型逻辑。

第三十九题

实现IsAny工具类型,用于判断类型T是否为any类型。具体的使用示例如下所示:

type IsAny = // 你的实现代码 type I0 = IsAny // false type I1 = IsAny // false type I2 = IsAny // true

IsAny工具类型实现:

type IsAny = 0 extends 1 & T ? true : false; type I0 = IsAny; // false type I1 = IsAny; // false type I2 = IsAny; // true

利用任何类型与any交叉都等于any实现

any类型是个 ”黑洞“ 会吞噬除了never类型之外的大多数类型。

type A0 = any & 1 // any type A1 = any & boolean // any type A2 = any & never // never

因此需要前置0 extends 交叉结果防止交叉结果为never类型的情况处理。

第四十题

实现AnyOf工具类型,只要数组中任意元素的类型非 Falsy 类型、{}类型或[]类型,则返回true,否则返回false。如果数组为空的话,则返回false。具体的使用示例如下所示:

type AnyOf = // 你的实现代码 type A0 = AnyOf; // false type A1 = AnyOf // false type A2 = AnyOf // true

AnyOf工具类型实现:

type NotEmptyObject = T extends {} ? ({} extends T ? false : true) : true; type Flasy = 0 | '' | false | []; type AnyOf = T extends [infer First, ...infer Rest] ? [First] extends [Flasy] ? AnyOf : NotEmptyObject : false; type A0 = AnyOf; // false type A1 = AnyOf; // false type A2 = AnyOf; // true type A3 = AnyOf; // true 非 Falsy 类型、 {} 类型或 [] 类型

NotEmptyObject工具类型判断是否为空对象{},如果是直接返回false;

type = Flasy定义属于Falsy的类型;

1、依次取出第一项,通过元组判断是否为Falsy类型(元组避免联合类型分发执行情况),如果当前项First满足Falsy类型则继续递归依次取出元素进行判断,否则再判断是否为空对象,如果是直接返回false,如果不是说明是非 Falsy类型、 {} 类型或 [] 类型。

第四十一题

实现Replace工具类型,用于实现字符串类型的替换操作。具体的使用示例如下所示:

type Replace< S extends string, From extends string, To extends string > = // 你的实现代码 type R0 = Replace // '' type R1 = Replace // "foofoo" type R2 = Replace // "foofoobar"

此外,继续实现ReplaceAll工具类型,用于实现替换所有满足条件的子串。具体的使用示例如下所示:

type ReplaceAll< S extends string, From extends string, To extends string > = // 你的实现代码 type R0 = ReplaceAll // '' type R1 = ReplaceAll // "foofoo" type R2 = ReplaceAll // "foofoofoo" type R3 = ReplaceAll // "fobarfobar"

Replace工具类型实现:

type Replace< S extends string, From extends string, To extends string > = S extends `${infer H}${From}${infer R}` ? `${H}${To}${R}` : S; // 测试用例 type R0 = Replace; // '' type R1 = Replace; // "foofoo" type R2 = Replace; // "foofoobar"

1、利用extends,配合infer配合字符串模板变量的写法就能提取出指定的子字符串,再将From改为To返回结果即可。

ReplaceAll工具类型实现:

type ReplaceAll< S extends string, From extends string, To extends string > = S extends `${infer H}${From}${infer R}` ? `${ReplaceAll}${To}${Replace}` : S; // 测试用例 type R0 = ReplaceAll // '' type R1 = ReplaceAll // "foofoo" type R2 = ReplaceAll // "foofoofoo" type R3 = ReplaceAll // "fobarfobar"

ReplaceAll工具类型取出子字符串之后利用递归。

第四十二题

实现IndexOf工具类型,用于获取数组类型中指定项的索引值。若不存在的话,则返回-1字面量类型。具体的使用示例如下所示:

type IndexOf = // 你的实现代码 type Arr = [1, 2, 3, 4, 5] type I0 = IndexOf // -1 type I1 = IndexOf // 0 type I2 = IndexOf // 2

IndexOf工具类型实现:

type IndexOf = A extends [ infer F, ...infer R ] ? F extends Item ? L['length'] : IndexOf : -1; type Arr = [1, 2, 3, 4, 5] type I0 = IndexOf // -1 type I1 = IndexOf // 0 type I2 = IndexOf // 2

构建数组来记录迭代到了哪一项,这样匹配到之后就能返回长度,就是索引值。

1、依次取数组的第一项与Item指定的值比较是否相等,找到就返回记录索引值的L数组;

2、找不到则继续增加L数组长度;

3、如果A extends [infer F, ...infer R]数组取完了,没有找到,直接返回-1。

第四十三题

实现一个Permutation工具类型,当输入一个联合类型时,返回一个包含该联合类型的全排列类型数组。具体的使用示例如下所示:

type Permutation = // 你的实现代码 // ["a", "b"] | ["b", "a"] type P0 = Permutation // ['a', 'b'] | ['b' | 'a'] // type P1 = ["a", "b", "c"] | ["a", "c", "b"] | ["b", "a", "c"] // | ["b", "c", "a"] | ["c", "a", "b"] | ["c", "b", "a"] type P1 = Permutation

Permutation工具类型实现:

type Permutation = [T] extends [never] ? [] : K extends K ? [K, ...Permutation] : never; type P0 = Permutation; // ['a', 'b'] | ['b' | 'a'] type P1 = Permutation; // => ["a", "b", "c"] | ["a", "c", "b"] | ["b", "a", "c"] | ["b", "c", "a"] // |["c", "a", "b"] | ["c", "b", "a"]

直接用传入'a' | 'b' | 'c'为例子说明:

这里简化 Exclude 后的结果 1、 ['a', ...Permutation] | ['b', ...Permutation] | ['c', ...Permutation] 2、 => ...Permutation 递归做再次分发后 => ['b', ...Permutation] | ['c', ...Permutation] => ['b', 'c'] | ['c', 'b'] 3、再与 1 结合也就是 (...会将结果展开) => ['a', 'b', 'c'] | ['a', 'c', 'b'] 再反复上面的 1 2 3 步骤得到最终结果 => type P1 = ["a", "b", "c"] | ["a", "c", "b"] | ["b", "a", "c"] | ["b", "c", "a"] |["c", "a", "b"] | ["c", "b", "a"] 第四十四题

实现Unpacked工具类型,用于对类型执行 “拆箱” 操作。具体的使用示例如下所示:

type Unpacked = // 你的实现代码 type T00 = Unpacked; // string type T01 = Unpacked; // string type T02 = Unpacked string>; // string type T03 = Unpacked; // string type T04 = Unpacked; // string type T05 = Unpacked; // any type T06 = Unpacked; // never

Unpacked工具类型实现:

type Unpacked = T extends (infer U)[] ? U : T extends (...args: any[]) => infer U ? U : T extends Promise ? U : T; // 测试用例 type T00 = Unpacked; // string type T01 = Unpacked; // string type T02 = Unpacked string>; // string type T03 = Unpacked; // string type T04 = Unpacked; // string type T05 = Unpacked; // any type T06 = Unpacked; // never

1、(infer U)[]处理数组类型,并返回数组类型的具体类型;

2、(...args: any[]) => infer U处理函数类型,推断拿到函数的返回类型;

3、Promise处理Promise类型,这里嵌套调用返回;

4、否则都不是上面三种类型其中一种,直接返回本身类型。

第四十五题

实现JsonifiedObject工具类型,用于对object对象类型进行序列化操作。具体的使用示例如下所示:

type JsonifiedObject = // 你的实现代码 type MyObject = { str: "literalstring", fn: () => void, date: Date, customClass: MyClass, obj: { prop: "property", clz: MyClass, nested: { attr: Date } }, } declare class MyClass { toJSON(): "MyClass"; } /** * type JsonifiedMyObject = { * str: "literalstring"; * fn: never; * date: string; * customClass: "MyClass"; * obj: JsonifiedObject; * } */ type JsonifiedMyObject = Jsonified; declare let ex: JsonifiedMyObject; const z1: "MyClass" = ex.customClass; const z2: string = ex.obj.nested.attr;

JsonifiedObject工具类型实现:

type JsonifiedObject = { [K in keyof T]: T[K] extends { toJSON(): infer Return } ? ReturnType : T[K] extends (...args: any[]) => any ? never : T[K] extends object ? JsonifiedObject : T[K]; }; declare class MyClass { toJSON(): 'MyClass'; } type MyObject = { str: 'literalstring'; fn: () => void; date: Date; customClass: MyClass; obj: { prop: 'property'; clz: MyClass; nested: { attr: Date }; }; }; // 测试用例 /** * type JsonifiedMyObject = { * str: "literalstring"; * fn: never; * date: string; * customClass: "MyClass"; * obj: JsonifiedObject; * } */ type JsonifiedMyObject = JsonifiedObject; declare let ex: JsonifiedMyObject; const z1: 'MyClass' = ex.customClass; const z2: string = ex.obj.nested.attr;

依次遍历对象,对一些属性类型做特殊处理

1、属性定义为MyClass类类型需要取的是toJSON函数属性的值,从新作为属性的字面量;

2、属性定义为函数类型需要改变成never类型;

3、深层对象需要递归遍历。

第四十六题

实现RequireAllOrNone工具类型,用于满足以下功能。即当设置age属性时,gender属性也会变成必填。具体的使用示例如下所示:

interface Person { name: string; age?: number; gender?: number; } type RequireAllOrNone = // 你的实现代码 const p1: RequireAllOrNone = { name: "lolo" }; const p2: RequireAllOrNone = { name: "lolo", age: 7, gender: 1 };

RequireAllOrNone工具类型实现:

type RequireAllOrNone = Omit & (Required | Partial); // 测试用例 const p1: RequireAllOrNone = { name: "lolo" }; const p2: RequireAllOrNone = { name: "lolo", age: 7, gender: 1 }; // Error: 缺少 gender 属性 const p3: RequireAllOrNone = { name: 'lolo', age: 1, };

题目的要求是如果对象设置了age属性,那么就需要gender属性;

1、反选排除K后的结果;

2、Required先选取K在T有的属性,然后把选取完的类型,将它们变成必选;

3、Partial)先创建有K属性的对象类型,属性类型为never,然后将它们都变成可选属性;

4、以上面的'age' | 'gender'为例:

{ age: number ; gender: number} | { age? : undefined | never; gender?: undefined | never}

这样如果对象设置了age属性,或者gender属性那么匹配前一对象类型,否则匹配后一对象类型;

5、name属性需要保留,因此使用反选,单独将name属性抽离出来。

第四十七题

实现RequireExactlyOne工具类型,用于满足以下功能。即只能包含age或gender属性,不能同时包含这两个属性。具体的使用示例如下所示:

interface Person { name: string; age?: number; gender?: number; } // 只能包含Keys中唯一的一个Key type RequireExactlyOne = // 你的实现代码 const p1: RequireExactlyOne = { name: "lolo", age: 7, }; const p2: RequireExactlyOne = { name: "lolo", gender: 1 }; // Error const p3: RequireExactlyOne = { name: "lolo", age: 7, gender: 1 };

RequireExactlyOne工具类型实现:

// // 想要构建成这个样子才可以满足条件 type Test = { name: string } & ({ age: number, gender?: never } | { age?: never, gender: number }) type RequireExactlyOne = Keys extends any ? Omit & Required & Partial : never; type TTT = | ({ name: string } & { age: number } & { gender?: never }) | ({ name: string } & { age?: never } & { gender: number });

利用联合类型extends实现分布执行,然后就是如何让联合类型规则只有其中某一个生效,那么其他属性就需要设置为可选never。

以传入'age' | 'gender'为例:

Keys用作分布执行,Keys extends any就是为了触发联合类型在条件中分发执行,K保留联合类型。

下面会被执行成

{ name: string } & { age: number } & { gender?: never } | { name: string } & { age?: never } & { gender: number }

简化之后也就得到我们满足条件的样子上面的Test。

第四十八题

实现ConsistsOnlyOf工具类型,用于判断LongString字符串类型是否由0个或多个Substring字符串类型组成。具体的使用示例如下所示:

type ConsistsOnlyOf = // 你的实现代码 type C0 = ConsistsOnlyOf //=> true type C1 = ConsistsOnlyOf //=> true type C2 = ConsistsOnlyOf //=> false type C3 = ConsistsOnlyOf //=> true

ConsistsOnlyOf工具类型实现:

type ConsistsOnlyOf< LongString extends string, Substring extends string > = LongString extends '' ? true : LongString extends `${Substring}${infer B}` ? ConsistsOnlyOf : false;

1、首先需要判断是否为空字符串,如果是直接返回true;

2、否则利用模板字符串语法,以及infer从开头一步步的匹配,减治思想。匹配成功则继续递归,直至字符串为空为止。

末尾

后续有新的题目会同步更新在这篇文章,对应的TypeScipt工具类型库推荐type-fest。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3