类型体操-中等
更加疯狂了。
获取函数返回类型
不使用 ReturnType 实现 TypeScript 的 ReturnType<T> 泛型。
例如:
const fn = (v: boolean) => {
if (v)
return 1
else
return 2
}
type a = MyReturnType<typeof fn> // 应推导出 "1 | 2"答案:
// 最初的问题在于没有考虑到函数的参数的问题,所以面对具有参数的函数这个类型就会失效
type MyReturnType<T extends () => any> = T extends () => (infer U) ? U : never
// 参考答案后将函数参数补上
type MyReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => (infer U) ? U : never
// 标准答案,使用了Function类型
type MyReturnType<T extends Function> = T extends (...args: any) => infer R ? R : never实现 Omit
不使用 Omit 实现 TypeScript 的 Omit<T, K> 泛型。
Omit 会创建一个省略 K 中字段的 T 对象。
例如:
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyOmit<Todo, 'description' | 'title'>
const todo: TodoPreview = {
completed: false,
}答案:
// kt in keyof T as kt extends K 将同时是T的键与K的值的字段选出,如果值存在返回never,否则返回ky,这样就得到了所需的所有键,最后将键与值组合。它的效果和Pick泛型正好是相反的。
type MyOmit<T, K extends keyof T> = {
[kt in keyof T as kt extends K ? never: kt] :T[kt]
}对象部分属性只读
实现一个泛型MyReadonly2<T, K>,它带有两种类型的参数T和K。
类型 K 指定 T 中要被设置为只读 (readonly) 的属性。如果未提供K,则应使所有属性都变为只读,就像普通的Readonly<T>一样。
例如:
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答案:
// 可以使用Omit与Pick泛型来实现,不过这里既然要追求刺激那就要贯彻到底不是吗?
type MyReadonly2<T, K extends keyof T = keyof T> =
{ readonly [key in keyof T as key extends K ? key : never]: T[key] }
& { [key in keyof T as key extends K ? never : key]: T[key] }对象属性只读(递归)
实现一个泛型 DeepReadonly<T>,它将对象的每个参数及其子对象递归地设为只读。
您可以假设在此挑战中我们仅处理对象。不考虑数组、函数、类等。但是,您仍然可以通过覆盖尽可能多的不同案例来挑战自己。
例如:
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`答案:
type DeepReadonly<T> =keyof T extends never
? T
: { readonly [k in keyof T]: DeepReadonly<T[k]> };元组转联合
实现泛型TupleToUnion<T>,它返回元组所有值的合集。
例如:
type Arr = ['1', '2', '3']
type Test = TupleToUnion<Arr> // expected to be '1' | '2' | '3'答案:
// 两种不同的实现
type TupleToUnion<T extends any[]> = T[number]
type TupleToUnion<T> = T extends Array<infer U> ? U : never可串联构造器
在 JavaScript 中我们经常会使用可串联(Chainable/Pipeline)的函数构造一个对象,但在 TypeScript 中,你能合理的给它赋上类型吗?
在这个挑战中,你可以使用任意你喜欢的方式实现这个类型 - Interface, Type 或 Class 都行。你需要提供两个函数 option(key, value) 和 get()。在 option 中你需要使用提供的 key 和 value 扩展当前的对象类型,通过 get 获取最终结果。
例如:
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
}
}答案:
最后一个元素
实现一个Last<T>泛型,它接受一个数组T并返回其最后一个元素的类型。
例如:
type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]
type tail1 = Last<arr1> // 应推导出 'c'
type tail2 = Last<arr2> // 应推导出 1答案:
// 这里使用...扩展数组的元素,并用[T["length"]]来获取到其最后一个元素
type Last<T extends any[]> = [...T][T["length"]]排除最后一项
实现一个泛型Pop<T>,它接受一个数组T,并返回一个由数组T的前 N-1 项(N 为数组T的长度)以相同的顺序组成的数组。
例如:
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]答案:
type Pop<T extends any[]> = T extends [...infer I, infer _] ? I : neverPromise.all
给函数PromiseAll指定类型,它接受元素为 Promise 或者类似 Promise 的对象的数组,返回值应为Promise<T>,其中T是这些 Promise 的结果组成的数组。
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)答案:
declare function PromiseAll<T extends any[]>(values: T ):
Promise<{[key in keyof T] : T[key] extends Promise<infer R> ? R : T[key] }>查找类型
有时,您可能希望根据某个属性在联合类型中查找类型。
在此挑战中,我们想通过在联合类型Cat | Dog中通过指定公共属性type的值来获取相应的类型。换句话说,在以下示例中,LookUp<Dog | Cat, 'dog'>的结果应该是Dog,LookUp<Dog | Cat, 'cat'>的结果应该是Cat。
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`答案:
type LookUp<U, T> = U extends T ? U : never去除左侧空白
实现 TrimLeft<T> ,它接收确定的字符串类型并返回一个新的字符串,其中新返回的字符串删除了原字符串开头的空白字符串。
例如:
type trimmed = TrimLeft<' Hello World '> // 应推导出 'Hello World '答案:
type TrimLeft<S extends string> =
S extends `${ ' ' | '\n' | '\t' }${infer R}` ? TrimLeft<R> : S去除两端空白字符
实现Trim<T>,它接受一个明确的字符串类型,并返回一个新字符串,其中两端的空白符都已被删除。
例如:
type trimmed = Trim<' Hello World '> // expected to be 'Hello World'答案:
// 我的实现
type Trim<S extends string> =
S extends `${' ' | '\n' | '\t'}${infer T}${' ' | '\n' | '\t'}`
? Trim<T> : S
// 答案
type Space = ' ' | '\t' | '\n';
type Trim<S extends string> =
S extends `${Space}${infer T}` | `${infer T}${Space}` ? Trim<T> : S;Capitalize
实现 Capitalize<T> 它将字符串的第一个字母转换为大写,其余字母保持原样。
例如:
type capitalized = Capitalize<'hello world'> // expected to be 'Hello world'答案:
TypeScript 在使用 infer 进行字符串解构时,会尽可能少地匹配内容(即“非贪婪”),以使整个模式能成功匹配,所以First是h不是he或者hel
type MyCapitalize<S extends string> =
S extends `${infer First}${infer Rest}`
? `${Uppercase<First>}${Rest}` : SReplace
实现 Replace<S, From, To> 将字符串 S 中的第一个子字符串 From 替换为 To 。
例如:
type replaced = Replace<'types are fun!', 'fun', 'awesome'> // 期望是 'types are awesome!'答案:
// 这个答案有一个问题,不能处理From是''的情况
type Replace<S extends string, From extends string, To extends string> =
S extends `${infer F}${From}${infer B}`
? `${F}${To}${B}`
: S
/*
if (S extends `${infer F}${From}${infer B}`) {
return `${F}${To}${B}`
} else {
return S
}
**/
// 答案
type Replace<S extends string, From extends string, To extends string> =
From extends ''
? S
: S extends `${infer V}${From}${infer R}`
? `${V}${To}${R}`
: SReplaceAll
实现 ReplaceAll<S, From, To> 将一个字符串 S 中的所有子字符串 From 替换为 To。
例如:
type replaced = ReplaceAll<'t y p e s', ' ', ''> // 期望是 'types'答案:
// 这个没有办法处理String一开始不是From的
type ReplaceAll<S extends string, From extends string, To extends string> =
S extends `${From}${infer Rest}`
? `${To}${ReplaceAll<Rest,From,To>}`
: S
// 没有考虑到''
type ReplaceAll<S extends string, From extends string, To extends string> =
S extends `${infer F}${From}${infer Rest}`
? `${F}${To}${ReplaceAll<Rest,From,To>}`
: S
// 答案
type ReplaceAll<S extends string, From extends string, To extends string> = From extends ''
? S
: S extends `${infer R1}${From}${infer R2}`
? `${R1}${To}${ReplaceAll<R2, From, To>}`
: S追加参数
> 由 @antfu 翻译
实现一个泛型 AppendArgument<Fn, A>,对于给定的函数类型 Fn,以及一个任意类型 A,返回一个新的函数 G。G 拥有 Fn 的所有参数并在末尾追加类型为 A 的参数。
type Fn = (a: number, b: string) => number
type Result = AppendArgument<Fn, boolean>
// 期望是 (a: number, b: string, x: boolean) => number答案:
type AppendArgument<Fn extends Function, A> =
// `...args` 在这里是运行时的展开,将参数收集到T元组中
Fn extends (...args: infer T) => infer R
// `...args` 在这里是类型声明语法,不是运行时的展开。`...T`,将T元组展开。
? (...args: [...T, A]) => R
: neverPermutation
实现联合类型的全排列,将联合类型转换成所有可能的全排列数组的联合类型。
type perm = Permutation<'A' | 'B' | 'C'>; // ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']答案: 这题肯定不是中等好吧
Length of String
计算字符串的长度,类似于 String#length 。
答案:我们想要得到一个字符串T的长度是不能直接T['length']的,这会返回number,不是一个具体的数字,而一个数组却可以
// 借助其他的泛型,将字符串转化为数组
type StringToArray<S extends string> =
S extends `${infer F}${infer R}`
? [F, ...StringToArray<R>]
: []
type LengthOfString<S extends string> = StringToArray<S>['length']Flatten
在这个挑战中,你需要写一个接受数组的类型,并且返回扁平化的数组类型。
例如:
type flatten = Flatten<[1, 2, [3, 4], [[5]([5.md)]]> // [1, 2, 3, 4, 5]答案:
type Flatten<T extends any[]> =
T extends [infer A, ...infer B]
? A extends any[]
? [...Flatten<A>, ...Flatten<B>]
: [A, ...Flatten<B>]
: []Append to object
实现一个为接口添加一个新字段的类型。该类型接收三个参数,返回带有新字段的接口类型。
例如:
type Test = { id: '1' }
type Result = AppendToObject<Test, 'value', 4> // expected to be { id: '1', value: 4 }答案:
// 太妙了
type AppendToObject<T, U extends keyof any, V> = {
[key in keyof T | U] : key extends keyof T
? T[key]
: V
}Absolute
实现一个接收string,number或bigInt类型参数的Absolute类型,返回一个正数字符串。
例如
type Test = -100;
type Result = Absolute<Test>; // expected to be "100"答案:
// 实际上根本就不用这样写
type AbsoluteString<T extends string> =
T extends `-${infer Rest}`
? Rest
: T
type AbsoluteNumber<T extends number> = any
type AbsoluteBigint<T extends bigint> = any
type Absolute<T extends string | number | bigint> =
T extends string
? AbsoluteString<T>
: T extends number
? AbsoluteNumber<T>
: T extends bigint
? AbsoluteBigint<T>
: never
// 这样就解决了
type Absolute<T extends number | string | bigint> =
`${T}` extends `-${infer Rest}`
? Rest
: `${T}`String to Union
实现一个将接收到的String参数转换为一个字母Union的类型。
例如
type Test = '123';
type Result = StringToUnion<Test>; // expected to be "1" | "2" | "3"答案:
// 终于做出来一道,好爽啊
type StringToArray<T extends string> =
T extends `${infer F}${infer Rest}`
? [F, ...StringToArray<Rest>]
: []
// 数组是元组的一种特例,所以这个泛型也可处理数组
type TupleToUnion<T extends any[]> = T[number]
// 结合已经实现的两个泛型
type StringToUnion<T extends string> = TupleToUnion<StringToArray<T>>Merge
例如:
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}答案:
// 整体的思路是对的,但是实现的过程有些问题
type Merge<F, S> = {
[key in keyof (F | S)] : key extends S
? S[key]
: F[key]
}
// 答案
type Merge<F, S> = {
[key in keyof F | keyof S] : key extends keyof S
? S[key]
: key extends keyof F
? F[key]
: never
}KebabCase
Replace the camelCase or PascalCase string with kebab-case.
FooBarBaz -> foo-bar-baz
For example
type FooBarBaz = KebabCase<"FooBarBaz">
const foobarbaz: FooBarBaz = "foo-bar-baz"
type DoNothing = KebabCase<"do-nothing">
const doNothing: DoNothing = "do-nothing"答案: First中的为第一个字符,Rest为剩余的字符,如果Rest是小写开头的,那么就不加-,反之
type KebabCase<S extends string> = S extends `${infer First}${infer Rest}`
? Rest extends Uncapitalize<Rest>
? `${Uncapitalize<First>}${KebabCase<Rest>}`
: `${Uncapitalize<First>}-${KebabCase<Rest>}`
: SDiff
获取两个接口类型中的差值属性。
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 }答案:
// 这种写法就是实现了一个Omit,既不优雅也不能实现
// type Diff<O, O1> = {[K in keyof O | keyof O1] : K extends keyof O & keyof O1 ? never : K extends keyof O ? O[K] : K extends keyof O1 ? O1[K] : never}
type Diff<O, O1> = Omit<O & O1, keyof (O | O1)>在对象中使用 | 与 &,与在非对象中使用存在语义上的差异。
在集合对象中使用联合类型 | ,官网 working-with-union-types 有如下说明:
> Notice that given two sets with corresponding facts about each set, only the intersection of those facts applies to the union of the sets themselves.
type Foo = {
name: string
age: string
}
type Bar = {
name: string
age: string
gender: number
}
type result = keyof (Foo | Bar) // "name" | "age"在集合对象中使用交集类型 & ,可以见 intersection-types 给出的 demo:
interface Colorful {
color: string;
}
interface Circle {
radius: number;
}
type ColorfulCircle = keyof (Colorful & Circle) // "color" | "radius"结合 & 与 | 的使用,我们能立马写出比如类型 diff
AnyOf
在类型系统中实现类似于 Python 中 any 函数。类型接收一个数组,如果数组中任一个元素为真,则返回 true,否则返回 false。如果数组为空,返回 false。
例如:
type Sample1 = AnyOf<[1, '', false, [], {}]> // expected to be true.
type Sample2 = AnyOf<[0, '', false, [], {}]> // expected to be false.答案:
type AnyOf<T extends any[]> =
T[number] extends 0 | '' | false | [] | {[key: string]: never}
? false
: true;IsNever
Implement a type IsNever, which takes input type T. If the type of resolves to never, return true, otherwise false.
For example:
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答案:
// 这里有一些小问题,如果T是never,那么返回的类型就是never,这有点奇怪
type IsNever<T> = T extends never ? true : false
type IsNever<T> = [T] extends [never] ? true : false所以这里的[T] extends [never]是什么?
IsNever<never> 中的 never 实际上是一个空的联合类型,一项都没有,所以 T extends ... 过程实际上被整体跳过了,所以最后的结果就是 never。[中文翻译](TS 类型体操笔记 - 296 Permutation类型体操 是关于 TS 类型编程的一系列挑战(编程题目)。 本文是 - 掘金)
IsUnion
Implement a type IsUnion, which takes an input type T and returns whether T resolves to a union type.
For example:
type case1 = IsUnion<string> // false
type case2 = IsUnion<string | number> // true
type case3 = IsUnion<[string | number]> // false