TypeScript 类型体操
00002-获取函数返回类型
不使用 ReturnType 实现 TypeScript 的 ReturnType<T>
泛型
// 我的实现:
type MyReturnType<T extends (...args: unknown[]) => unknown> = T extends (...args: unknown[]) => infer P ? P : never;
// 用例:
const fn = (v: boolean) => {
if (v) return 1;
else return 2;
};
type a = MyReturnType<typeof fn>; // 应推导出 "1 | 2"
00003-Omit
不使用 Omit 实现 TypeScript 的 Omit<T, K>
泛型。Omit 会创建一个省略 K 中字段的 T 对象。
// 我的实现:
type MyOmit<T, K extends keyof T> = {
[P in keyof T as P extends K ? never : P]: T[P];
};
// 使用Exclude实现
// type MyOmit<T, K extends keyof T> = {
// [P in Exclude<keyof T, K>]: T[P]
// }
// 用例:
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = MyOmit<Todo, 'description' | 'title'>;
const todo: TodoPreview = {
completed: false,
};
00004-Pick
Pick<T, K>
从类型 T 中选出符合 K 的属性,构造一个新的类型
// 实现:
type MyPick<T, K extends keyof T> = {
[key in K]: T[key];
};
// 用例:
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = MyPick<Todo, 'title' | 'completed'>;
const todo: TodoPreview = {
title: 'Clean room',
completed: false,
};
00007-只读
泛型 Readonly<T>
会接收一个 泛型参数,并返回一个完全一样的类型,只是所有属性都会是只读 (readonly) 的
// 实现:
type MyReadonly<T> = {
readonly [key in keyof T]: T[key];
};
// 用例:
interface Todo {
title: string
description: string
}
const todo: MyReadonly<Todo> = {
title: "Hey",
description: "foobar"
}
todo.title = "Hello" // Error: cannot reassign a readonly property
todo.description = "barFoo" // Error: cannot reassign a readonly property
00008-指定类型只读
实现一个泛型MyReadonly2<T, K>
,它带有两种类型的参数T和K。
类型 K 指定 T 中要被设置为只读 (readonly) 的属性。如果未提供K,则应使所有属性都变为只读,就像普通的Readonly<T>
一样。
// 我的实现:
type MyReadonly2<T, K extends keyof T = keyof T> = {
[p in keyof T as p extends K ? never : p]: T[p];
} & {
readonly [p in K]: T[p];
};
// 因为第二个泛型可能为空,所以需要通过 = 来赋默认值
// 用例:
interface Todo {
title: string;
description: string;
completed: boolean;
}
const todo: MyReadonly2<Todo, 'title' | 'description'> = {
title: 'Hey',
description: 'foobar',
completed: false,
};
todo.title = 'Hello'; // Error: cannot reassign a readonly property
todo.description = 'barFoo'; // Error: cannot reassign a readonly property
todo.completed = true; // OK
00009-深度只读
实现一个泛型 DeepReadonly<T>
,它将对象的每个参数及其子对象递归地设为只读。
您可以假设在此挑战中我们仅处理对象。不考虑数组、函数、类等。但是,您仍然可以通过覆盖尽可能多的不同案例来挑战自己。
// 我的实现:
type DeepReadonly<T> = keyof T extends never
? T
: {
readonly [K in keyof T]: DeepReadonly<T[K]>;
};
// 用例:
type X = {
x: {
a: 1;
b: 'hi';
};
y: 'hey';
};
type Expected = {
readonly x: {
readonly a: 1;
readonly b: 'hi';
};
readonly y: 'hey';
};
type Todo = DeepReadonly<X>; // should be same as `Expected`
00010-元组转合集
实现泛型TupleToUnion<T>
,它返回元组所有值的合集
// 我的实现:
type MyParameters<T extends (...args: unknown[]) => unknown> = T extends (...args: infer P) => unknown ? P : never;
// 用例:
const foo = (arg1: string, arg2: number): void => {};
type FunctionParamsType = MyParameters<typeof foo>; // [arg1: string, arg2: number]
00011-元组转换对象
将一个元组类型转换为对象类型,这个对象类型的键/值和元组中的元素对应
// 实现:
type TupleToObject<T extends readonly PropertyKey[]> = {
[K in T[number]]: K;
};
// ts内置的类型PropertyKey, 类型定义是declare type PropertyKey = string | number | symbol;
// 元组的索引都是number类型的,所以当使用T[number]取出T中所有number类型的索引时,可以一次全部取到所有元素的类型
// 用例:
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const
// expected { 'tesla': 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}
type result = TupleToObject<typeof tuple>
00012-可串联构造器
在 JavaScript 中我们经常会使用可串联(Chainable/Pipeline)的函数构造一个对象,但在 TypeScript 中,你能合理的给它赋上类型吗?
在这个挑战中,你可以使用任意你喜欢的方式实现这个类型 - Interface, Type 或 Class 都行。你需要提供两个函数 option(key, value) 和 get()。在 option 中你需要使用提供的 key 和 value 扩展当前的对象类型,通过 get 获取最终结果。
// 我的实现:
type Chainable<T = {}> = {
option: <K extends string, V>(
key: K extends keyof T ? (V extends T[K] ? never : K) : K,
value: V,
) => Chainable<Omit<T, K> & Record<K, V>>;
get: () => T;
};
// 可以使用 T = {} 来作为默认值,甚至默认参数与默认返回值,再通过递归传递 T 即可实现递归全局记录
// 为了约束 key 不可重复必须范型传入,value 是任意类型范型不做约束直接透传, 利用Record创建键值对
// K extends keyof T ? V extends T[K] ? never : K : K 限制相同key的类型不能重复
// 直接 & 联合并不能将相同 key 的类型覆盖,因此用 Omit 去掉前一个类型中相同的 key
// 用例:
declare const config: Chainable;
const result = config.option('foo', 123).option('name', 'type-challenges').option('bar', { value: 'Hello World' }).get();
// 期望 result 的类型是:
interface Result {
foo: number;
name: string;
bar: {
value: string;
};
}
00014-第一个元素类型
实现一个First<T>
泛型,它接受一个数组T并返回它的第一个元素的类型
// 实现:
type First<T extends readonly PropertyKey[]> = T[number] extends never ? never : T[0];
// type First<T extends any[]> = T extends [infer A, ...infer rest] ? A : never
// 用例:
type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]
type head1 = First<arr1> // 应推导出 'a'
type head2 = First<arr2> // 应推导出 3
00015-最后一个元素
实现一个Last<T>
泛型,它接受一个数组T并返回其最后一个元素的类型。
// 实现:
type Last<T extends unknown[]> = [unknown, ...T][T['length']];
// type Last<T extends any[]> = T extends [...infer _, infer L] ? L : never;
// 用例:
type arr1 = ['a', 'b', 'c'];
type arr2 = [3, 2, 1];
type tail1 = Last<arr1>; // 应推导出 'c'
type tail2 = Last<arr2>; // 应推导出 1
00016-排除最后一项
实现一个泛型Pop<T>
,它接受一个数组T,并返回一个由数组T的前 N-1 项(N 为数组T的长度)以相同的顺序组成的数组。
// 实现:
type Pop<T extends unknown[]> = T extends [...infer rest, infer _] ? rest : never;
// type Last<T extends any[]> = T extends [...infer _, infer L] ? L : never;
// 用例:
type arr1 = ['a', 'b', 'c', 'd'];
type arr2 = [3, 2, 1];
type re1 = Pop<arr1>; // expected to be ['a', 'b', 'c']
type re2 = Pop<arr2>; // expected to be [3, 2]
00018-获取元组长度
创建一个Length
泛型,这个泛型接受一个只读的元组,返回这个元组的长度。
// 实现:
type Length<T extends readonly PropertyKey[]> = T['length']
// 用例:
type tesla = ['tesla', 'model 3', 'model X', 'model Y']
type spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT']
type teslaLength = Length<tesla> // expected 4
type spaceXLength = Length<spaceX> // expected 5
00020-Promise.all
给函数PromiseAll指定类型,它接受元素为 Promise 或者类似 Promise 的对象的数组,返回值应为Promise<T>
,其中T是这些 Promise 的结果组成的数组。
// 实现:
declare function PromiseAll<T extends any[]>(arr: T): Promise<{ [K in keyof T]: Awaited<T[K]> }>;
// 用例:
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise<string>((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
// 应推导出 `Promise<[number, 42, string]>`
const p = PromiseAll([promise1, promise2, promise3] as const);
00043-Exclude
实现内置的 Exclude<T, U>
类型,但不能直接使用它本身。
从联合类型 T 中排除 U 中的类型,来构造一个新的类型。
00062-查找类型
有时,您可能希望根据某个属性在联合类型中查找类型。
在此挑战中,我们想通过在联合类型Cat | Dog
中通过指定公共属性type的值来获取相应的类型。换句话说,在以下示例中,LookUp<Dog | Cat, 'dog'>
的结果应该是Dog
,LookUp<Dog | Cat, 'cat'>
的结果应该是Cat。
// 我的实现:
type LookUp<U, T> = U extends { type: T } ? U : never;
//
// type LookUp2<U, T extends string> = {
// [K in T]: U extends { type: T } ? U : never
// }[T]
// 用例:
interface Cat {
type: 'cat';
breeds: 'Abyssinian' | 'Shorthair' | 'Curl' | 'Bengal';
}
interface Dog {
type: 'dog';
breeds: 'Hound' | 'Brittany' | 'Bulldog' | 'Boxer';
color: 'brown' | 'white' | 'black';
}
type MyDog = LookUp<Cat | Dog, 'dog'>; // expected to be `Dog`
00106-去除左侧空白
实现 TrimLeft<T>
,它接收确定的字符串类型并返回一个新的字符串,其中新返回的字符串删除了原字符串开头的空白字符串。
// 我的实现:
type Space = ' ' | '\n' | '\t';
type TrimLeft<T extends string> = T extends `${Space}${infer R}` ? TrimLeft<R> : T;
// 用例:
type trimed = TrimLeft<' Hello World '>; // 应推导出 'Hello World '
00108-去除两端空白字符
实现Trim<T>
,它接受一个明确的字符串类型,并返回一个新字符串,其中两端的空白符都已被删除。
// 我的实现:
type Spcae = ' ' | '\n' | '\t';
type Trim<S extends string> = S extends `${Spcae}${infer R}` | `${infer R}${Spcae}` ? Trim<R> : S;
// 使用Exclude实现
// type MyOmit<T, K extends keyof T> = {
// [P in Exclude<keyof T, K>]: T[P]
// }
// 用例:
type trimed = Trim<' Hello World '>; // expected to be 'Hello World'
00110-Capitalize
实现 Capitalize<T>
它将字符串的第一个字母转换为大写,其余字母保持原样。
// 我的实现:
type Capitalize<S extends string> = S extends `${infer F}${infer R}` ? `${Uppercase<F>}${R}` : S;
// 用例:
type capitalized = Capitalize<'hello world'>; // expected to be 'Hello world'
00116-Replace
实现 Replace<S, From, To>
将字符串 S 中的第一个子字符串 From 替换为 To 。
// 我的实现:
type Replace<T extends string, From extends string, To extends string> = T extends `${infer prefix}${From}${infer suffix}`
? `${prefix}${To}${suffix}`
: T;
// 用例:
type replaced = Replace<'types are fun!', 'fun', 'awesome'>; // 期望是 'types are awesome!'
00119-ReplaceAll
实现 ReplaceAll<S, From, To>
将一个字符串 S 中的所有子字符串 From 替换为 To。
// 我的实现:
type ReplaceAll<S extends string, From extends string, To extends string> = S extends `${infer prefix}${From}${infer suffix}`
? `${prefix}${To}${ReplaceAll<suffix, From, To>}`
: S;
// 用例:
type replaced = ReplaceAll<'t y p e s', ' ', ''>; // 期望是 'types'
00189-Awaited
假如我们有一个 Promise 对象,这个 Promise 对象会返回一个类型。在 TS 中,我们用 Promise 中的 T 来描述这个 Promise 返回的类型。请你实现一个类型,可以获取这个类型。
例如:Promise<ExampleType>
,请你返回 ExampleType
类型。
// 实现:
type MyAwaited<T extends PromiseLike<any>> =
T extends PromiseLike<infer U> ? (U extends PromiseLike<any> ? MyAwaited<U> : U) : never;
// type MyAwaited<T> = T extends PromiseLike<infer U> ? MyAwaited<U> : T;
// 用例:
type ExampleType = Promise<string>;
type Result = MyAwaited<ExampleType>; // string
00191-追加参数
实现一个泛型 AppendArgument<Fn, A>
,对于给定的函数类型 Fn,以及一个任意类型 A,返回一个新的函数 G。G 拥有 Fn 的所有参数并在末尾追加类型为 A 的参数。
// 我的实现:
type AppendArgument<Fn, A> = Fn extends (...args: infer Args) => infer R ? (...args: [...Args, A]) => R : never;
// 用例:
type Fn = (a: number, b: string) => number;
type Result = AppendArgument<Fn, boolean>;
// 期望是 (a: number, b: string, x: boolean) => number
00268-If
实现一个 IF 类型,它接收一个条件类型 C ,一个判断为真时的返回类型 T ,以及一个判断为假时的返回类型 F。 C 只能是 true 或者 false, T 和 F 可以是任意类型。
// 实现:
type If<C extends boolean, T, F> = C extends true ? T : F;
// 用例:
type A = If<true, 'a', 'b'> // expected to be 'a'
type B = If<false, 'a', 'b'> // expected to be 'b'
00296-Permutation
实现联合类型的全排列,将联合类型转换成所有可能的全排列数组的联合类型。
// 实现:
type Permutation<T, K = T> = [T] extends [never] ? [] : K extends T ? [K, ...Permutation<Exclude<T, K>>] : never;
// 用例:
type perm = Permutation<'A' | 'B' | 'C'>;
// ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']
00298-字符串的长度
计算字符串的长度,类似于 String#length 。
// 我的实现:
type StringLength<S extends string, T extends string[] = []> = S extends `${infer C}${infer rest}`
? StringLength<rest, [...T, C]>
: T['length'];
// 用例:
type len = StringLength<'hello'>; // 5
00459-Flatten
在这个挑战中,你需要写一个接受数组的类型,并且返回扁平化的数组类型。
// 我的实现:
type Flatten<T extends unknown[]> = T extends [infer F, ...infer rest]
? F extends unknown[]
? [...Flatten<F>, ...Flatten<rest>]
: [F, ...Flatten<rest>]
: [];
// 用例:
type flatten = Flatten<[1, 2, [3, 4], [[[5]]]]>; // [1, 2, 3, 4, 5]
00527-Append to object
实现一个为接口添加一个新字段的类型。该类型接收三个参数,返回带有新字段的接口类型。
// 我的实现:
type AppendToObject<T, K extends keyof any, V> = {
[prop in keyof T | K]: prop extends keyof T ? T[prop] : V
}
// 用例:
type Test = { id: '1' }
type Result = AppendToObject<Test, 'value', 4> // expected to be { id: '1', value: 4 }
00529-Absolute
实现一个接收string,number或bigInt类型参数的Absolute类型,返回一个正数字符串。
// 我的实现:
type Absolute<T extends string | number | bigint > = `${T}` extends `-${infer U}` ? `${U}` : `${T}`
// 用例:
type Test = -100;
type Result = Absolute<Test>; // expected to be "100"
00531-String to Union
实现一个将接收到的String参数转换为一个字母Union的类型。
// 实现:
type StringToUnion<T extends string> = T extends `${infer U}${infer rest}` ? U | StringToUnion<rest> : never
// 用例:
type Test = '123';
type Result = StringToUnion<Test>; // expected to be "1" | "2" | "3"
00533-Concat
在类型系统里实现 JavaScript 内置的 Array.concat 方法,这个类型接受两个参数,返回的新数组类型应该按照输入参数从左到右的顺序合并为一个新的数组。
// 实现:
type Concat<T extends readonly any[], U extends readonly any[]> = [...T, ...U];
// 用例:
type Result = Concat<[1], [2]> // expected to be [1, 2]
00599-Merge
将两个类型合并成一个类型,第二个类型的键会覆盖第一个类型的键。
import { withKeys } from "vue";
// 我的实现:
type Merge<T, U> = {
[K in keyof T | keyof U] : K extends keyof U ? U[K] : K extends keyof T ? T[K] : never;
}
// 用例:
type foo = {
name: string;
age: string;
}
type coo = {
age: number;
sex: string
}
type Result = Merge<foo,coo>; // expected to be {name: string, age: number, sex: string}
00612-KebabCase
Replace the camelCase
or PascalCase
string with kebab-case
.
FooBarBaz -> foo-bar-baz
// 我的实现:
type KebabCase<T extends string> = T extends `${infer Char}${infer Rest}`
? Rest extends `${Uncapitalize<Rest>}`
? `${Uncapitalize<Char>}${KebabCase<Rest>}`
: `${Uncapitalize<Char>}-${KebabCase<Rest>}`
: T;
// Uncapitalize 用于将首字母转换为小写
// 用例:
type FooBarBaz = KebabCase<'FooBarBaz'>;
const foobarbaz: FooBarBaz = 'foo-bar-baz';
type DoNothing = KebabCase<'do-nothing'>;
const doNothing: DoNothing = 'do-nothing';
00645-diff
获取两个接口类型中的差值属性。
// 我的实现:
type Diff<T, U> = Omit<T & U, keyof (T | U)>
// 用例:
type Foo = {
a: string;
b: number;
}
type Bar = {
a: string;
c: boolean
}
type Result1 = Diff<Foo,Bar> // { b: number, c: boolean }
type Result2 = Diff<Bar,Foo> // { b: number, c: boolean }
00898-Includes
在类型系统里实现 JavaScript 的 Array.includes 方法,这个类型接受两个参数,返回的类型要么是 true 要么是 false。
// 我的实现:
type Includes<T extends readonly any[], K> = K extends T[number] ? true : false;
// 点赞最多的
type _Includes<T extends readonly any[], U> = {
[P in T[number]]: true;
}[U] extends true
? true
: false;
// 用例:
type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'>; // expected to be `false`
00949-AnyOf
在类型系统中实现类似于 Python 中 any 函数。类型接收一个数组,如果数组中任一个元素为真,则返回 true,否则返回 false。如果数组为空,返回 false。
// 我的实现:
type AnyOf<T extends unknown[]> = T[number] extends 0 | '' | false | [] | { [key in string]: never } ? false : true;
// 用例:
type Sample1 = AnyOf<[1, '', false, [], {}]>; // expected to be true.
type Sample2 = AnyOf<[0, '', false, [], {}]>; // expected to be false.
001042-isNever
实现一个类型 isNever 判断传入的类型 T 是否是 Never
// 实现:
type IsNever<T extends unknown> = [T] extends [never] ? true : false;
// 用例:
type A = IsNever<never>; // expected to be true
type B = IsNever<undefined>; // expected to be false
type C = IsNever<null>; // expected to be false
type D = IsNever<[]>; // expected to be false
type E = IsNever<number>; // expected to be false
03057-push
在类型系统里实现通用的 Array.push
// 我的实现:
type Push<T extends readonly unknown[], K> = [...T, K];
// 用例:
type Result = Push<[1, 2], '3'>; // [1, 2, '3']
why use unknow insteadof any The unknown type represents any value. This is similar to the any type, but is safer because it’s not legal to do anything with an unknown value: https://www.typescriptlang.org/docs/handbook/2/functions.html#unknown
03060-Unshift
实现类型版本的 Array.unshift
// 我的实现:
type Unshift<T extends readonly unknown[], K> = [K, ...T];
// 用例:
type Result = Unshift<[1, 2], 0>; // [0, 1, 2]
03312-Parameters
实现内置的Parameters
类型
// 我的实现:
type MyParameters<T extends (...args: unknown[]) => unknown> = T extends (...args: infer P) => unknown ? P : never;
// 用例:
const foo = (arg1: string, arg2: number): void => {};
type FunctionParamsType = MyParameters<typeof foo>; // [arg1: string, arg2: number]