typescript刷题记录
题目一
解决如下错误
type User = { id: number; kind: string;};function makeCustomer<T extends User>(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类型的约束,但是也有可能有其他情况,比如{id:1, kind:'2',name:'test'},所以返回值中缺少了name
方法一
直接把剩余的u的key返回出去
type User = { id: number; kind: string;};function makeCustomer<T extends User>(u: T): T { return { ...u, id: u.id, kind: 'customer' }}方法二
function makeCustomer<T extends User>(u: T): ReturnMake<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' }}
type ReturnMake<T extends User, U> = { [K in keyof R as K extends keyof T ? K: never]: U[K]}遍历 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 { // error: 运算符“+”不能应用于类型“number”和“string | number”。ts(2365) return a + b; // error as b can be number | string }}解决方案:函数重载
function f(a:string, b:string ):stringfunction f(a:number, b:number):number;function f(a: string | number, b: string | number) { if (typeof a === 'string' || typeof b === 'string') { return a + ':' + b; // no error but b can be number! } else { return a + b; // error as b can be number | string }}题目三(SetOptional || SetRequired)
如何定义一个 SetOptional 工具类型,支持把给定的keys对应的属性变成可选的?对应的使用示例如下所示:
type Foo = { a: number; b?: string; c: boolean;}
// 测试用例type SomeOptional = SetOptional<Foo, 'a' | 'b'>;
// type SomeOptional = {// a?: number; // 该属性已变成可选的// b?: string; // 保持不变// c: boolean;// }解决方法
type Foo = { a: number; b?: string; c: boolean;}
// 测试用例type SomeOptional = SetOptional<Foo, 'a' | 'b'>;
// type SomeOptional = {// a?: number; // 该属性已变成可选的// b?: string; // 保持不变// c: boolean;// }
type SetOptional<T, R extends keyof T> = Omit<T,R> & Partial<Pick<T,R>>setRequried也是一样的如下
type SetRequired<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;题目四(ConditionalPick)
Pick<T, K extends keyof T>的作用是将某个类型中的子属性挑出来,变成包含这个类型部分属性的子类型。
interface Todo { title: string; description: string; completed: boolean;}
type TodoPreview = Pick<Todo, "title" | "completed">;
const todo: TodoPreview = { title: "Clean room", completed: false};那么如何定义一个 ConditionalPick工具类型,支持根据指定的 Condition 条件来生成新的类型,对应的使用示例如下:
interface Example { a: string; b: string | number; c: () => void; d: {};}
// 测试用例:type StringKeysOnly = ConditionalPick<Example, string>;//=> {a: string}解决方案
1、in keyof遍历 V 泛型;
2、通过类型断言判断 V[K] 对应键值是否约束于传入的 string如果是 true 那么断言成返回遍历的当前 K,否则为 never。
返回 never 在 TypeScript 编译器中,会默认认为这是个用不存在的类型,也相当于没有这个 K 会被过滤,对应值则是 V[K] 获取。
interface Example { a: string; b: string | number; c: () => void; d: {};}
// 测试用例:type StringKeysOnly = ConditionalPick<Example, string>;//=> {a: string}
type ConditionalPick<T,R> = { [K in keyof T as T[K] extends R ? K : never ]: T[K]}题目五(追加args)
定义一个工具类型 AppendArgument,为已有的函数类型增加指定类型的参数,新增的参数名是x,将作为新函数类型的第一个参数。具体的使用示例如下所示:
type Fn = (a: number, b: string) => numbertype AppendArgument<F, A> = // 你的实现代码
type FinalFn = AppendArgument<Fn, boolean>// (x: boolean, a: number, b: string) => number解决方案1 (使用泛型工具)
使用泛型工具Parameters以及ReturnType
type Fn = (a: number, b: string) => number
type FinalFn = AppendArgument<Fn, boolean>// (x: boolean, a: number, b: string) => number
type AppendArgument<F extends (...args:any[]) => any, A> = (arg1:A , ...args2:Parameters<F>) => ReturnType<F> // 你的实现代码解决方案2 (使用infer)
infer推导拿到参数类型T返回值类型为R,再从新返回一个新函数arg1参数为A,...args参数类型为前面推导保留的T,返回值即R。
type AppendArgument<F extends (...args:any[]) => any, A> = F extends (...args:infer T)=> infer R ? (agr1:A, ...args:T)=>R : never题目五(数组类型拍平)
定义一个NativeFlat工具类型,支持把数组类型拍平(扁平化)。具体的使用示例如下所示:
type NaiveFlat<T extends any[]> = // 你的实现代码
// 测试用例:type NaiveResult = NaiveFlat<[['a'], [['b', 'c']], ['d']]>
// NaiveResult的结果: "a" | "b" | "c" | "d"解决方案(递归)
type NaiveFlat<T extends any[]> = T extends (infer R)[] ? (R extends any[] ? NaiveFlat<R>: R ): never// 你的实现代码
// 测试用例:type NaiveResult = NaiveFlat<[['a'], [['b', 'c']], ['d']]>
// NaiveResult的结果: "a" | "b" | "c" | "d"1、首先需要在约束条件中使用infer关键字推导出 T 传入的数组类型,并用 P 保存数组类型。
2、三元嵌套判断R类型是否约束于类型any[]如果还是是数组继续递归遍历调用NaiveFlat<R>并传入R,放 R类型不满足 any[],返回最后的扁平完成R类型所以得到最终联合类型"a" | "b" | "c" | "d" 。
题目六 (数组不为空)
定义NonEmptyArray工具类型,用于确保数据非空数组。
type NonEmptyArray<T> = // 你的实现代码const a: NonEmptyArray<string> = [] // 将出现编译错误const b: NonEmptyArray<string> = ['Hello TS'] // 非空数据,正常使用解决方案
const a: NonEmptyArray<string> = [] // 将出现编译错误const b: NonEmptyArray<string> = ['Hello TS'] // 非空数据,正常使用
type NonEmptyArray<T> = [T, ...T[]][T, ...T[]]确保第一项一定是T,[...T[]],为剩余数组类型。
题目七
定义一个JoinStrArray工具类型,用于根据指定的Separator分隔符,对字符串数组类型进行拼接。具体的使用示例如下所示:
type JoinStrArray<Arr extends string[], Separator extends string> = // 你的实现代码
// 测试用例type Names = ["Sem", "Lolo", "Kaquko"]type NamesComma = JoinStrArray<Names, ","> // "Sem,Lolo,Kaquko"type NamesSpace = JoinStrArray<Names, " "> // "Sem Lolo Kaquko"type NamesStars = JoinStrArray<Names, "⭐️"> // "Sem⭐️Lolo⭐️Kaquko"解决方法
// 测试用例type Names = ["a", "b", "c"]type NamesComma = JoinStrArray<Names, ","> // "Sem,Lolo,Kaquko"type NamesSpace = JoinStrArray<Names, " "> // "Sem Lolo Kaquko"type NamesStars = JoinStrArray<Names, "⭐️"> // "Sem⭐️Lolo⭐️Kaquko"
type JoinStrArray<Arr extends string[], Separator extends string> = Arr extends [infer A,...infer B] ? (`${A extends string ? A : ''}${B extends [string, ...string[]] ? `${Separator}${JoinStrArray<B, Separator>}` : ''}`):'' // 你的实现代码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<V extends string> = // 你的实现代码
// 测试用例Trim<' semlinker '>//=> 'semlinker'解决方案
type TrimLeft<V extends string> = V extends ` ${infer R}` ? TrimLeft<R> : V;type TrimRight<V extends string> = V extends `${infer R} ` ? TrimRight<R> : V;
type Trim<V extends string> = TrimLeft<TrimRight<V>>;
// 测试用例type Result = Trim<' semlinker '>//=> 'semlinker'题目九
实现一个IsEqual工具类型,用于比较两个类型是否相等。具体的使用示例如下所示:
type IsEqual<A, B> = // 你的实现代码
// 测试用例type E0 = IsEqual<1, 2>; // falsetype E1 = IsEqual<{ a: 1 }, { a: 1 }> // truetype E2 = IsEqual<[1], []>; // false解决方案
// 测试用例type E0 = IsEqual<1, 2>; // falsetype E1 = IsEqual<{ a: 1 }, { a: 1 }> // truetype E2 = IsEqual<[1], []>; // falsetype IsEqual<A, B> = [A] extends [B]? ([B] extends [A]? true:false ): false // 你的实现代码题目十
以下两者都是相类似的类型推倒
实现一个获取数组第一个位置的类型 HEAD
// 测试用例type H0 = Head<[]> // nevertype H1 = Head<[1]> // 1type H2 = Head<[3, 2]> // 3type Head<T extends Array<any>> = T extends [infer A, ...infer B] ? A :never// 你的实现代码实现一个获取数组最后一个位置的类型TAIL
// 测试用例type T0 = Tail<[]> // []type T1 = Tail<[1, 2]> // [2]type T2 = Tail<[1, 2, 3, 4, 5]> // [2, 3, 4, 5]type Tail<T extends Array<any>> = T extends [...any, infer A] ? A : never // 你的实现代码题目十一
实现一个Unshift工具类型,用于把指定类型E作为第一个元素添加到 T 数组类型中。具体的使用示例如下所示:
type Unshift<T extends any[], E> = // 你的实现代码
// 测试用例type Arr0 = Unshift<[], 1>; // [1]type Arr1 = Unshift<[1, 2, 3], 0>; // [0, 1, 2, 3]复制代码实现方法
type Unshift<T extends any[], E> = [E, ...T];
// 测试用例type Arr0 = Unshift<[], never>; // [1]type Arr1 = Unshift<[1, 2, 3], 0>; // [0, 1, 2, 3]复制代码新建一个数组,第一项类型为E,剩余使用...T连接。
题目十二
实现一个Shift工具类型,用于移除T数组类型中的第一个类型。具体的使用示例如下所示:
type Shift<T extends any[]> = // 你的实现代码
// 测试用例type S0 = Shift<[1, 2, 3]>type S1 = Shift<[string,number,boolean]>实现方案
// 测试用例type S0 = Shift<[1, 2, 3]>type S1 = Shift<[string,number,boolean]>type Shift<T extends any[]> = T extends [infer A, ...infer B] ? B : [] // 你的实现代码...infer B去除第一项之后的集合,使用变量B保存该类型。如果满足约束,返回剩余参数类型,也就是B。
题目十三
实现一个Push工具类型,用于把指定类型E作为第最后一个元素添加到T数组类型中。具体的使用示例如下所示:
type Push<T extends any[], V> = // 你的实现代码
// 测试用例type Arr0 = Push<[], 1> // [1]type Arr1 = Push<[1, 2, 3], 4> // [1, 2, 3, 4]解决方案
// 测试用例type Arr0 = Push<[], 1> // [1]type Arr1 = Push<[1, 2, 3], 4> // [1, 2, 3, 4]type Push<T extends any[], E> = [...T, E] // 你的实现代码题目十四
实现一个Includes工具类型,用于判断指定的类型E是否包含在T数组类型中。具体的使用示例如下所示:
type Includes<T extends Array<any>, E> = // 你的实现代码type I0 = Includes<[], 1> // falsetype I1 = Includes<[2, 2, 3, 1], 2> // truetype I2 = Includes<[2, 3, 3, 1], 1> // true解决方法
type I0 = Includes<[], 1> // falsetype I1 = Includes<[2, 2, 3, 1], 2> // truetype I2 = Includes<[2, 3, 3, 1], 1> // true
type Includes<T extends Array<any>, E> = E extends T[number] ? true :false这里T[number]可以理解返回T数组元素的类型,比如传入的泛型T为[2, 2, 3, 1],那么T[number]被解析为:2 | 2 | 3 | 1。
*题目十五
实现一个UnionToIntersection工具类型,用于把联合类型转换为交叉类型。具体的使用示例如下所示:
type UnionToIntersection<U> = // 你的实现代码
// 测试用例type U0 = UnionToIntersection<string | number> // nevertype U1 = UnionToIntersection<{ name: string } | { age: number }> // { name: string; } & { age: number; }解决方案
// 测试用例type U0 = UnionToIntersection<string | number> // nevertype U1 = UnionToIntersection<{ name: string } | { age: number }> // { name: string; } & { age: number; }
type UnionToIntersection<U> = ( U extends unknown ? (props: U)=>void : never) extends (props2: infer P) => void ? P : never1、extends unknown始终为true,默认进入到分发情况
2、会声明一个以U为入参类型的函数类型A,即(props: U) => void,该函数约束于以props2类型为入参的函数类型B,即(props2: infer P) => void。
3、如果函数A能继承函数B则 返回infer P声明的P,否则返回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> = T extends { a: infer U, b: infer U } ? U : never;type T10 = Foo<{ a: string, b: string }>; // stringtype T11 = Foo<{ a: string, b: number }>; // string | number在逆变的位置上,同一个类型多个候选类型会被推断为交叉类型:
type Bar<T> = T extends { a: (x: infer U) => void, b: (x: infer U) => void } ? U : never;type T20 = Bar<{ a: (x: string) => void, b: (x: string) => void }>; // stringtype T21 = Bar<{ a: (x: string) => void, b: (x: number) => void }>; // string & number题目十六(元组转化为对象)
ts内置方法实现
Exclude 排除类型
例子:
type Dir='1'|'2'|'3'|'4'
// type dir1 = "3" | "4"type dir1=Exclude<Dir,'1'|'2'>实现:
type MyExclude<T, R> = T extends R ? never :TPick 获取 key 类型
type MyPick<T, R extends keyof T> = { [Key in R]: T[Key]}Omit 排除 key 类型
type MyOmit<T, R extends keyof T> = MyPick<T, MyExclude<keyof T, R>>Readonly 属性只读
type MyReadonly<T> = { readonly [Key in keyof T]: T[Key]}Required 属性必选
type MyRequired<T> = { [Key in keyof T]-?:T[Key]}Partial 属性可选
type MyPartial<T> = { [Key in keyof T]?: T[Key]}ReturnType 获取函数返回类型
type MyReturnType<T extends (...args:any[])=>any> = T extends (...args:any[]) => infer R ? R :neverParameters 获取函数参数类型
type MyParameters<T extends (...args:any[])=>any> = T extends (...args:infer B) => any? B:neverts 实用范型工具
OptionalRequired 部分属性必选
实现一
/* * @Description: 部分属性必选 */
export type OptionalRequired<T,R extends keyof T> = { [key in R]-?: T[key]} & { [key in Exclude<keyof T, R>]: T[key]}
type TestObj = { a?:string, b:number, c?:object}
type OptionRequiredObjType = OptionalRequired<TestObj,'a' | 'c'>实现二
export type OptionRequired<T,R extends keyof T> = Required<T> & Omit<T, R>OptionalReadonly 部分属性只读
实现一
/* * @Description: 部分属性只读 */export type OptionalReadonly<T,R extends keyof T> = { readonly [key in R]: T[key]} & { [key in Exclude<keyof T, R>]: T[key]}
type TestObj = { a?:string, b:number, c?:object}
type OptionReadonlyObjType = OptionalReadonly<TestObj,'a' | 'c'>实现二
export type OptionalReadonly<T,R extends keyof T> = Omit<T,R> & Readonly<Pick<T,R>>OptionalPartial 部分属性可选
实现一
/* * @Description: 部分可选 */
type OptionalPartial<T, R extends keyof T> = { [key in R]?: T[key]} & { [key in Exclude<keyof T, R>]: T[key]}
type TestObj = { a:string, b:number, c:object}
type OptionPartialObjType = OptionalPartial<TestObj,'a' | 'c'>实现二
type OptionalPartial<T, R extends keyof T> = Omit<T, R> & Partial<Pick<T,R>>;