TS 练习题
做练习题,巩固基础 💪
重学TS 2.0 系列
「重学TS 2.0 」 系列练习题
第一题
题目
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
类型,即包含但不限于User
类型,所以返回为T
类型可能不仅仅只有id
和kind
,所以要么修改返回值类型,要么就修改返回值
- 修改返回值类型:
type User = {
id: number;
kind: string;
};
function makeCustomer<T extends User>(u: T): User {
return {
id: u.id,
kind: 'customer'
}
}
- 修改返回值:
type User = {
id: number;
kind: string;
};
function makeCustomer<T extends User>(u: T): T {
return {
...u,
id: u.id,
kind: 'customer'
}
}
第二题
题目
本道题我们希望参数 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
}
}
f(2, 3); // Ok
f(1, 'a'); // Error
f('a', 2); // Error
f('a', 'b') // Ok
解答
- 使用函数重载:
function f(a: number, b: number): number;
function f(a: string, b: string): string;
function f(a, b) {
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
}
}
f(2, 3); // Ok
f(1, 'a'); // Error
f('a', 2); // Error
f('a', 'b') // Ok
- 泛型解决
function f<T extends string | number>(a: T, b: T) {
if (typeof a === 'string') {
return a + ':' + b; // no error but b can be number!
} else {
return (a as number) + (b as number); // error as b can be number | string
}
}
f(2, 3); // Ok
f(1, 'a'); // Error
f('a', 2); // Error
f('a', 'b') // Ok
第三题
题目
在 掌握 TS 这些工具类型,让你开发事半功倍 这篇文章中,阿宝哥介绍了 TS 内置的工具类型:Partial<T>
,它的作用是将某个类型里的属性全部变为可选项 ?
。
interface Todo {
title: string;
description: string;
}
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
return { ...todo, ...fieldsToUpdate };
}
// lib.es5.d.ts
type Partial<T> = {
[P in keyof T]?: T[P];
};
那么如何定义一个 SetOptional
工具类型,支持把给定的 keys 对应的属性变成可选的?对应的使用示例如下所示:
type Foo = {
a: number;
b?: string;
c: boolean;
}
// 测试用例
type SomeOptional = SetOptional<Foo, 'a' | 'b'>;
// type SomeOptional = {
// a?: number; // 该属性已变成可选的
// b?: string; // 保持不变
// c: boolean;
// }
在实现 SetOptional
工具类型之后,如果你感兴趣,可以继续实现 SetRequired
工具类型,利用它可以把指定的 keys 对应的属性变成必填的。对应的使用示例如下所示:
type Foo = {
a?: number;
b: string;
c?: boolean;
}
// 测试用例
type SomeRequired = SetRequired<Foo, 'b' | 'c'>;
// type SomeRequired = {
// a?: number;
// b: string; // 保持不变
// c: boolean; // 该属性已变成必填
// }
解答
SetOptional
思路:
- 先使用
Omit<Foo, 'a' | 'b'>
得到排除了a | b
的Foo
,再使用Partial<Pick<Foo, 'a' | 'b'>>
将Foo
中的a | b
设置为可选,最后将两种类型合并&
返回即可。
type SetOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
SetRequired
思路:
- 同
SetOptional
,只不过将后面的Partial
改为Required
即可。
type SetRequired<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
第四题
题目
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}
解答
[K in keyof T as (T[K] extends U ? K : never)]
: 这里使用as
巧妙的对T[K]
进行二次判断,使:
左边返回符合T[K] extends U
的K
;还有一个知识点:当 key 为
never
null
和undefined
时不会生效。
type ConditionalPick<T, U> = {
[K in keyof T as (T[K] extends U ? K : never)]: T[K]
}
interface Example {
a: string;
b: string | number;
c: () => void;
d: {};
}
// 测试用例:
type StringKeysOnly = ConditionalPick<Example, string>;
//=> {a: string}
第五题
题目
定义一个工具类型 AppendArgument
,为已有的函数类型增加指定类型的参数,新增的参数名是 x
,将作为新函数类型的第一个参数。具体的使用示例如下所示:
type Fn = (a: number, b: string) => number
type AppendArgument<F, A> = // 你的实现代码
type FinalFn = AppendArgument<Fn, boolean>
// (x: boolean, a: number, b: string) => number
解答
- 使用
Parameters<F>
和ReturnType<F>
工具类型提取出Fn
函数类型的参数类型和返回值类型,再与x: A
组合成新的类型即可
// 使用 Parameters<F> 和 ReturnType<F> 工具类型
type AppendArgument<F extends (...args: any) => any, A> = (x: A, ...args: Parameters<F>) => ReturnType<F>;
type Fn = (a: number, b: string) => number
type FinalFn = AppendArgument<Fn, boolean>
// (x: boolean, a: number, b: string) => number
- 使用
infer
,因为Parameters
和ReturnType
都是通过infer
实现的;这里直接使用infer
,在一个函数类型里声明了两个变量,即可同时收集参数类型和返回值类型
// 使用 infer
type AppendArgument<F extends (...args: any) => any, T> = F extends (...args: infer P) => infer R ? (x: T, ...args: P) => R : never;
type Fn = (a: number, b: string) => number
type FinalFn = AppendArgument<Fn, boolean>
// (x: boolean, a: number, b: string) => number
第六题
题目
定义一个 NativeFlat
工具类型,支持把数组类型拍平(扁平化)。具体的使用示例如下所示:
type NaiveFlat<T extends any[]> = // 你的实现代码
// 测试用例:
type NaiveResult = NaiveFlat<[['a'], ['b', 'c'], ['d']]>
// NaiveResult的结果: "a" | "b" | "c" | "d"
在完成 NaiveFlat
工具类型之后,在继续实现 DeepFlat
工具类型,以支持多维数组类型:
type DeepFlat<T extends any[]> = unknown // 你的实现代码
// 测试用例
type Deep = [['a'], ['b', 'c'], [['d']], [[[['e']]]]];
type DeepTestResult = DeepFlat<Deep>
// DeepTestResult: "a" | "b" | "c" | "d" | "e"
知识点铺垫
['a', 'b', 'c'][number]
可以取出"a" | "b" | "c"
。演变过程如下:
// 使用下标取数组元素
type A = ['a', 'b', 'c'][0] // type A = "a"
// | 可以取多个下标
type B = ['a', 'b', 'c'][0 | 1] // type B = "a" | "b"
// number 表示取所有下标,即取出数组中所有的类型
type C = ['a', 'b', 'c'][number] // type C = "a" | "b" | "c"
{[K in keyof ['a', 'b', 'c']]}
得到{0: 'a', 1: 'b', 2: 'c', length: 3, ...}
, 再加上[number]
就可以提取出数组的联合类型:
type StringArr = ['a', 'b', 'c'];
// 根据数组类型生成对象类型
type KeyofArrMap = {
[K in keyof StringArr]: StringArr[K]
}
/* 这里会将 Array 的属性都暴露出来
type KeyofArrMap = {
[x: number]: "a" | "b" | "c";
0: "a";
1: "b";
2: "c";
length: 3;
toString: () => string;
toLocaleString: () => string;
pop: () => "a" | "b" | "c";
...
}
*/
// 加上 [number] 即可将所有数字所有对应的值提取出来
type KeyofArrType = KeyofArrMap[number] // type KeyofArrType = "a" | "b" | "c"
解答
NaiveFlat
实现:
type NaiveFlat<T extends any[]> = {
[P in keyof T]: T[P] extends any[] ? T[P][number] : T[P]
}[number]
// 测试用例:
type NaiveResult = NaiveFlat<[['a'], ['b', 'c'], ['d']]>
// NaiveResult的结果: "a" | "b" | "c" | "d"
DeepFlat
实现:
type DeepFlat<T extends any[]> = {
[P in keyof T]: T[P] extends any[] ? DeepFlat<T[P]> : T[P]
}[number]
// 测试用例
type Deep = [['a'], ['b', 'c'], [['d']], [[[['e']]]]];
type DeepTestResult = DeepFlat<Deep>
// DeepTestResult: "a" | "b" | "c" | "d" | "e"
第七题
题目
使用类型别名定义一个 EmptyObject
类型,使得该类型只允许空对象赋值:
type EmptyObject = {}
// 测试用例
const shouldPass: EmptyObject = {}; // 可以正常赋值
const shouldFail: EmptyObject = { // 将出现编译错误
prop: "TS"
}
在通过 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) // 将出现编译错误
知识点铺垫
PropertyKey
系统内置索引签名参数类型,源码:
type PropertyKey = string | number | symbol
解答
EmptyObject
type EmptyObject = {
// type PropertyKey = string | number | symbol
[K in PropertyKey]: never
}
// 测试用例
const shouldPass: EmptyObject = {}; // 可以正常赋值
const shouldFail: EmptyObject = { // 将出现编译错误
prop: "TS"
}
takeSomeTypeOnly
type SomeType = {
prop: string
}
// 将 T2 中和 T1 不相同的 K 设置为 never
type Exclusive<T1, T2 extends T1> = {
[K in keyof T2]: K extends keyof T1 ? T2[K] : never
}
// 更改以下函数的类型定义,让它的参数只允许严格SomeType类型的值
function takeSomeTypeOnly<T extends SomeType>(x: Exclusive<SomeType, T>) { return x }
// 测试用例:
const x = { prop: 'a' };
takeSomeTypeOnly(x) // 可以正常调用
const y = { prop: 'a', addditionalProp: 'x' };
takeSomeTypeOnly(y) // 将出现编译错误
第八题
题目
定义 NonEmptyArray
工具类型,用于确保数据非空数组。
type NonEmptyArray<T> = // 你的实现代码
const a: NonEmptyArray<string> = [] // 将出现编译错误
const b: NonEmptyArray<string> = ['Hello TS'] // 非空数据,正常使用
解答
解法一
type NonEmptyArray<T> = [T, ...T[]]
解法二
type NonEmptyArray<T> = T[] & { 0: T }
@antfu TS类型体操
简单(13题)
实现 Pick
实现 TS 内置的 Pick<T, K>
,但不可以使用它。
从类型 T
中选择出属性 K
,构造成一个新的类型。
例如:
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyPick<Todo, 'title' | 'completed'>
const todo: TodoPreview = {
title: 'Clean room',
completed: false,
}
解答:
/*
4 - 实现 Pick
-------
by Anthony Fu (@antfu) #简单 #union #built-in
### 题目
> 欢迎 PR 改进翻译质量。
实现 TS 内置的 `Pick<T, K>`,但不可以使用它。
**从类型 `T` 中选择出属性 `K`,构造成一个新的类型**。
例如:
``ts
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyPick<Todo, 'title' | 'completed'>
const todo: TodoPreview = {
title: 'Clean room',
completed: false,
}
``
> 在 Github 上查看:https://tsch.js.org/4/zh-CN
*/
/* _____________ 你的代码 _____________ */
type MyPick<T, K extends keyof T> = {
[P in K]: T[P]
}
/* _____________ 测试用例 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<Expected1, MyPick<Todo, 'title'>>>,
Expect<Equal<Expected2, MyPick<Todo, 'title' | 'completed'>>>,
// @ts-expect-error
MyPick<Todo, 'title' | 'completed' | 'invalid'>,
]
interface Todo {
title: string
description: string
completed: boolean
}
interface Expected1 {
title: string
}
interface Expected2 {
title: string
completed: boolean
}
/* _____________ 下一步 _____________ */
/*
> 分享你的解答:https://tsch.js.org/4/answer/zh-CN
> 查看解答:https://tsch.js.org/4/solutions
> 更多题目:https://tsch.js.org/zh-CN
*/
实现 Readonly
不要使用内置的 Readonly<T>
,自己实现一个。
该 Readonly
会接收一个 泛型参数,并返回一个完全一样的类型,只是所有属性都会被 readonly
所修饰。
也就是不可以再对该对象的属性赋值。
例如:
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
解答:
/*
7 - 实现 Readonly
-------
by Anthony Fu (@antfu) #简单 #built-in #readonly #object-keys
### 题目
> 欢迎 PR 改进翻译质量。
不要使用内置的`Readonly<T>`,自己实现一个。
该 `Readonly` 会接收一个 _泛型参数_,并返回一个完全一样的类型,只是所有属性都会被 `readonly` 所修饰。
也就是不可以再对该对象的属性赋值。
例如:
``ts
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
``
> 在 Github 上查看:https://tsch.js.org/7/zh-CN
*/
/* _____________ 你的代码 _____________ */
type MyReadonly<T extends {}> = {
readonly [K in keyof T]: T[K]
}
/* _____________ 测试用例 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<MyReadonly<Todo1>, Readonly<Todo1>>>,
]
interface Todo1 {
title: string
description: string
completed: boolean
meta: {
author: string
}
}
/* _____________ 下一步 _____________ */
/*
> 分享你的解答:https://tsch.js.org/7/answer/zh-CN
> 查看解答:https://tsch.js.org/7/solutions
> 更多题目:https://tsch.js.org/zh-CN
*/
元组转换为对象
传入一个元组类型,将这个元组类型转换为对象类型,这个对象类型的键/值都是从元组中遍历出来。
例如:
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const
type result = TupleToObject<typeof tuple> // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}
解答:
/*
11 - 元组转换为对象
-------
by sinoon (@sinoon) #简单 #object-keys
### 题目
> 欢迎 PR 改进翻译质量。
传入一个元组类型,将这个元组类型转换为对象类型,这个对象类型的键/值都是从元组中遍历出来。
例如:
`ts
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const
type result = TupleToObject<typeof tuple> // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}
`
> 在 Github 上查看:https://tsch.js.org/11/zh-CN
*/
/* _____________ 你的代码 _____________ */
type TupleToObject<T extends readonly (string | number | symbol)[]> = {
[K in T[number]]: K
}
/* _____________ 测试用例 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const
const tupleNumber = [1, 2, 3, 4] as const
const tupleMix = [1, '2', 3, '4'] as const
type cases = [
Expect<Equal<TupleToObject<typeof tuple>, { tesla: 'tesla'; 'model 3': 'model 3'; 'model X': 'model X'; 'model Y': 'model Y' }>>,
Expect<Equal<TupleToObject<typeof tupleNumber>, { 1: 1; 2: 2; 3: 3; 4: 4 }>>,
Expect<Equal<TupleToObject<typeof tupleMix>, { 1: 1; '2': '2'; 3: 3; '4': '4' }>>,
]
// @ts-expect-error
type error = TupleToObject<[[1, 2], {}]>
/* _____________ 下一步 _____________ */
/*
> 分享你的解答:https://tsch.js.org/11/answer/zh-CN
> 查看解答:https://tsch.js.org/11/solutions
> 更多题目:https://tsch.js.org/zh-CN
*/
第一个元素
实现一个通用 First<T>
,它接受一个数组 T
并返回它的第一个元素的类型。
例如:
type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]
type head1 = First<arr1> // expected to be 'a'
type head2 = First<arr2> // expected to be 3
解答:
/*
14 - 第一个元素
-------
by Anthony Fu (@antfu) #简单 #array
### 题目
> 欢迎 PR 改进翻译质量。
实现一个通用`First<T>`,它接受一个数组`T`并返回它的第一个元素的类型。
例如:
`ts
type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]
type head1 = First<arr1> // expected to be 'a'
type head2 = First<arr2> // expected to be 3
`
> 在 Github 上查看:https://tsch.js.org/14/zh-CN
*/
/* _____________ 你的代码 _____________ */
type First<T extends any[]> = T extends [] ? never : T[0]
/* _____________ 测试用例 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<First<[3, 2, 1]>, 3>>,
Expect<Equal<First<[() => 123, { a: string }]>, () => 123>>,
Expect<Equal<First<[]>, never>>,
Expect<Equal<First<[undefined]>, undefined>>,
]
type errors = [
// @ts-expect-error
First<'notArray'>,
// @ts-expect-error
First<{ 0: 'arrayLike' }>,
]
/* _____________ 下一步 _____________ */
/*
> 分享你的解答:https://tsch.js.org/14/answer/zh-CN
> 查看解答:https://tsch.js.org/14/solutions
> 更多题目:https://tsch.js.org/zh-CN
*/
获取元组长度
创建一个通用的 Length
,接受一个 readonly
的数组,返回这个数组的长度。
例如:
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
解答:
/*
18 - 获取元组长度
-------
by sinoon (@sinoon) #简单 #tuple
### 题目
> 欢迎 PR 改进翻译质量。
创建一个通用的`Length`,接受一个`readonly`的数组,返回这个数组的长度。
例如:
``ts
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
``
> 在 Github 上查看:https://tsch.js.org/18/zh-CN
*/
/* _____________ 你的代码 _____________ */
type Length<T extends readonly any[]> = T['length']
/* _____________ 测试用例 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
const tesla = ['tesla', 'model 3', 'model X', 'model Y'] as const
const spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT'] as const
type cases = [
Expect<Equal<Length<typeof tesla>, 4>>,
Expect<Equal<Length<typeof spaceX>, 5>>,
// @ts-expect-error
Length<5>,
// @ts-expect-error
Length<'hello world'>,
]
/* _____________ 下一步 _____________ */
/*
> 分享你的解答:https://tsch.js.org/18/answer/zh-CN
> 查看解答:https://tsch.js.org/18/solutions
> 更多题目:https://tsch.js.org/zh-CN
*/
Exclude
实现内置的Exclude <T, U>
类型,但不能直接使用它本身。
从联合类型T中排除U的类型成员,来构造一个新的类型。
例如:
type Result = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'
解答:
/*
43 - Exclude
-------
by Zheeeng (@zheeeng) #简单 #built-in #union
### 题目
> 欢迎 PR 改进翻译质量。
实现内置的Exclude <T, U>类型,但不能直接使用它本身。
> 从联合类型T中排除U的类型成员,来构造一个新的类型。
例如:
``ts
type Result = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'
``
> 在 Github 上查看:https://tsch.js.org/43/zh-CN
*/
/* _____________ 你的代码 _____________ */
type MyExclude<T, U> = T extends U ? never : T
/* _____________ 测试用例 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<MyExclude<'a' | 'b' | 'c', 'a'>, 'b' | 'c'>>,
Expect<Equal<MyExclude<'a' | 'b' | 'c', 'a' | 'b'>, 'c'>>,
Expect<Equal<MyExclude<string | number | (() => void), Function>, string | number>>,
]
/* _____________ 下一步 _____________ */
/*
> 分享你的解答:https://tsch.js.org/43/answer/zh-CN
> 查看解答:https://tsch.js.org/43/solutions
> 更多题目:https://tsch.js.org/zh-CN
*/
Awaited
假如我们有一个 Promise 对象,这个 Promise 对象会返回一个类型。在 TS 中,我们用 Promise<T>
中的 T
来描述这个 Promise 返回的类型。请你实现一个类型,可以获取这个类型。
例如:Promise<ExampleType>
,请你返回 ExampleType
类型。
type ExampleType = Promise<string>
type Result = MyAwaited<ExampleType> // string
解答:
/*
189 - Awaited
-------
by Maciej Sikora (@maciejsikora) #简单 #promise #built-in
### 题目
假如我们有一个 Promise 对象,这个 Promise 对象会返回一个类型。在 TS 中,我们用 Promise<T> 中的 T 来描述这个 Promise 返回的类型。请你实现一个类型,可以获取这个类型。
例如:`Promise<ExampleType>`,请你返回 ExampleType 类型。
``ts
type ExampleType = Promise<string>
type Result = MyAwaited<ExampleType> // string
``
> 这个挑战来自于 [@maciejsikora](https://github.com/maciejsikora) 的文章:[original article](https://dev.to/macsikora/advanced-typescript-exercises-question-1-45k4)
> 在 Github 上查看:https://tsch.js.org/189/zh-CN
*/
/* _____________ 你的代码 _____________ */
type MyAwaited<T extends Pick<Promise<any>, 'then'>> = T extends { then: (onfulfilled: (arg: infer R) => any) => any }
? R extends Pick<Promise<any>, 'then'> ? MyAwaited<R> : R
: T;
/* _____________ 测试用例 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type X = Promise<string>
type Y = Promise<{ field: number }>
type Z = Promise<Promise<string | number>>
type Z1 = Promise<Promise<Promise<string | boolean>>>
type T = { then: (onfulfilled: (arg: number) => any) => any }
type Te = MyAwaited<T>
type cases = [
Expect<Equal<MyAwaited<X>, string>>,
Expect<Equal<MyAwaited<Y>, { field: number }>>,
Expect<Equal<MyAwaited<Z>, string | number>>,
Expect<Equal<MyAwaited<Z1>, string | boolean>>,
Expect<Equal<MyAwaited<T>, number>>,
]
// @ts-expect-error
type error = MyAwaited<number>
/* _____________ 下一步 _____________ */
/*
> 分享你的解答:https://tsch.js.org/189/answer/zh-CN
> 查看解答:https://tsch.js.org/189/solutions
> 更多题目:https://tsch.js.org/zh-CN
*/
If
实现一个 IF
类型,它接收一个条件类型 C
,一个判断为真时的返回类型 T
,以及一个判断为假时的返回类型 F
。 C
只能是 true
或者 false
, T
和 F
可以是任意类型。
例如:
type A = If<true, 'a', 'b'> // expected to be 'a'
type B = If<false, 'a', 'b'> // expected to be 'b'
解答:
/*
268 - If
-------
by Pavel Glushkov (@pashutk) #简单 #utils
### 题目
实现一个 `IF` 类型,它接收一个条件类型 `C` ,一个判断为真时的返回类型 `T` ,以及一个判断为假时的返回类型 `F`。 `C` 只能是 `true` 或者 `false`, `T` 和 `F` 可以是任意类型。
例如:
``ts
type A = If<true, 'a', 'b'> // expected to be 'a'
type B = If<false, 'a', 'b'> // expected to be 'b'
``
> 在 Github 上查看:https://tsch.js.org/268/zh-CN
*/
/* _____________ 你的代码 _____________ */
type If<C extends boolean, T, F> = C extends true ? T : F;
/* _____________ 测试用例 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<If<true, 'a', 'b'>, 'a'>>,
Expect<Equal<If<false, 'a', 2>, 2>>,
]
// @ts-expect-error
type error = If<null, 'a', 'b'>
/* _____________ 下一步 _____________ */
/*
> 分享你的解答:https://tsch.js.org/268/answer/zh-CN
> 查看解答:https://tsch.js.org/268/solutions
> 更多题目:https://tsch.js.org/zh-CN
*/
Concat
在类型系统里实现 JavaScript 内置的 Array.concat
方法,这个类型接受两个参数,返回的新数组类型应该按照输入参数从左到右的顺序合并为一个新的数组。
例如:
type Result = Concat<[1], [2]> // expected to be [1, 2]
解答:
/*
533 - Concat
-------
by Andrey Krasovsky (@bre30kra69cs) #简单 #array
### 题目
在类型系统里实现 JavaScript 内置的 `Array.concat` 方法,这个类型接受两个参数,返回的新数组类型应该按照输入参数从左到右的顺序合并为一个新的数组。
例如:
``ts
type Result = Concat<[1], [2]> // expected to be [1, 2]
``
> 在 Github 上查看:https://tsch.js.org/533/zh-CN
*/
/* _____________ 你的代码 _____________ */
type Concat<T extends any[], U extends any[]> = [...T, ...U]
/* _____________ 测试用例 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<Concat<[], []>, []>>,
Expect<Equal<Concat<[], [1]>, [1]>>,
Expect<Equal<Concat<[1, 2], [3, 4]>, [1, 2, 3, 4]>>,
Expect<Equal<Concat<['1', 2, '3'], [false, boolean, '4']>, ['1', 2, '3', false, boolean, '4']>>,
]
/* _____________ 下一步 _____________ */
/*
> 分享你的解答:https://tsch.js.org/533/answer/zh-CN
> 查看解答:https://tsch.js.org/533/solutions
> 更多题目:https://tsch.js.org/zh-CN
*/
Includes
此题不简单
在类型系统里实现 JavaScript 的 Array.includes
方法,这个类型接受两个参数,返回的类型要么是 true
要么是 false
。
例如:
type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`
解答:
/*
898 - Includes
-------
by null (@kynefuk) #简单 #array
### 题目
在类型系统里实现 JavaScript 的 `Array.includes` 方法,这个类型接受两个参数,返回的类型要么是 `true` 要么是 `false`。
例如:
``ts
type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`
``
> 在 Github 上查看:https://tsch.js.org/898/zh-CN
*/
/* _____________ 你的代码 _____________ */
/** 比较两个类型是否相等 */
type IsEqual<T, U> =
(<G>() => G extends T ? 1 : 2) extends
(<G>() => G extends U ? 1 : 2)
? true
: false;
type Includes<T extends readonly any[], U> = T extends [infer F, ...infer R]
? IsEqual<F, U> extends true
? true : Includes<R, U>
: false;
/* _____________ 测试用例 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Kars'>, true>>,
Expect<Equal<Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'>, false>>,
Expect<Equal<Includes<[1, 2, 3, 5, 6, 7], 7>, true>>,
Expect<Equal<Includes<[1, 2, 3, 5, 6, 7], 4>, false>>,
Expect<Equal<Includes<[1, 2, 3], 2>, true>>,
Expect<Equal<Includes<[1, 2, 3], 1>, true>>,
Expect<Equal<Includes<[{}], { a: 'A' }>, false>>,
Expect<Equal<Includes<[boolean, 2, 3, 5, 6, 7], false>, false>>,
Expect<Equal<Includes<[true, 2, 3, 5, 6, 7], boolean>, false>>,
Expect<Equal<Includes<[false, 2, 3, 5, 6, 7], false>, true>>,
Expect<Equal<Includes<[{ a: 'A' }], { readonly a: 'A' }>, false>>,
Expect<Equal<Includes<[{ readonly a: 'A' }], { a: 'A' }>, false>>,
Expect<Equal<Includes<[1], 1 | 2>, false>>,
Expect<Equal<Includes<[1 | 2], 1>, false>>,
Expect<Equal<Includes<[null], undefined>, false>>,
Expect<Equal<Includes<[undefined], null>, false>>,
]
/* _____________ 下一步 _____________ */
/*
> 分享你的解答:https://tsch.js.org/898/answer/zh-CN
> 查看解答:https://tsch.js.org/898/solutions
> 更多题目:https://tsch.js.org/zh-CN
*/
Push
在类型系统里实现通用的 Array.push
。
例如:
type Result = Push<[1, 2], '3'> // [1, 2, '3']
解答:
/*
3057 - Push
-------
by jiangshan (@jiangshanmeta) #简单 #array
### 题目
在类型系统里实现通用的 ```Array.push``` 。
例如:
``typescript
type Result = Push<[1, 2], '3'> // [1, 2, '3']
``
> 在 Github 上查看:https://tsch.js.org/3057/zh-CN
*/
/* _____________ 你的代码 _____________ */
type Push<T extends any[], U> = [...T, U];
/* _____________ 测试用例 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<Push<[], 1>, [1]>>,
Expect<Equal<Push<[1, 2], '3'>, [1, 2, '3']>>,
Expect<Equal<Push<['1', 2, '3'], boolean>, ['1', 2, '3', boolean]>>,
]
/* _____________ 下一步 _____________ */
/*
> 分享你的解答:https://tsch.js.org/3057/answer/zh-CN
> 查看解答:https://tsch.js.org/3057/solutions
> 更多题目:https://tsch.js.org/zh-CN
*/
Unshift
实现类型版本的 Array.unshift
。
例如:
type Result = Unshift<[1, 2], 0> // [0, 1, 2,]
解答:
/*
3060 - Unshift
-------
by jiangshan (@jiangshanmeta) #简单 #array
### 题目
实现类型版本的 ```Array.unshift```。
例如:
``typescript
type Result = Unshift<[1, 2], 0> // [0, 1, 2,]
``
> 在 Github 上查看:https://tsch.js.org/3060/zh-CN
*/
/* _____________ 你的代码 _____________ */
type Unshift<T extends any[], U> = [U, ...T];
/* _____________ 测试用例 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<Unshift<[], 1>, [1]>>,
Expect<Equal<Unshift<[1, 2], 0>, [0, 1, 2]>>,
Expect<Equal<Unshift<['1', 2, '3'], boolean>, [boolean, '1', 2, '3']>>,
]
/* _____________ 下一步 _____________ */
/*
> 分享你的解答:https://tsch.js.org/3060/answer/zh-CN
> 查看解答:https://tsch.js.org/3060/solutions
> 更多题目:https://tsch.js.org/zh-CN
*/
Parameters
实现内置的 Parameters<T>
类型,而不是直接使用它,可参考TypeScript官方文档。
例如:
const foo = (arg1: string, arg2: number): void => {}
type FunctionParamsType = MyParameters<typeof foo> // [arg1: string, arg2: number]
解答:
/*
3312 - Parameters
-------
by midorizemi (@midorizemi) #简单 #infer #tuple #built-in
### 题目
实现内置的 Parameters<T> 类型,而不是直接使用它,可参考[TypeScript官方文档](https://www.typescriptlang.org/docs/handbook/utility-types.html#parameterstype)。
例如:
``ts
const foo = (arg1: string, arg2: number): void => {}
type FunctionParamsType = MyParameters<typeof foo> // [arg1: string, arg2: number]
``
> 在 Github 上查看:https://tsch.js.org/3312/zh-CN
*/
/* _____________ 你的代码 _____________ */
type MyParameters<T extends (...args: any[]) => any> = T extends (...args: infer R) => any ? R : never;
/* _____________ 测试用例 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
const foo = (arg1: string, arg2: number): void => {}
const bar = (arg1: boolean, arg2: { a: 'A' }): void => {}
const baz = (): void => {}
type cases = [
Expect<Equal<MyParameters<typeof foo>, [string, number]>>,
Expect<Equal<MyParameters<typeof bar>, [boolean, { a: 'A' }]>>,
Expect<Equal<MyParameters<typeof baz>, []>>,
]
/* _____________ 下一步 _____________ */
/*
> 分享你的解答:https://tsch.js.org/3312/answer/zh-CN
> 查看解答:https://tsch.js.org/3312/solutions
> 更多题目:https://tsch.js.org/zh-CN
*/
中等
获取函数返回类型
不使用 ReturnType
实现 TypeScript 的 ReturnType<T>
泛型。
例如:
const fn = (v: boolean) => {
if (v)
return 1
else
return 2
}
type a = MyReturnType<typeof fn> // 应推导出 "1 | 2"
解答:
/*
2 - 获取函数返回类型
-------
by Anthony Fu (@antfu) #中等 #infer #built-in
### 题目
不使用 `ReturnType` 实现 TypeScript 的 `ReturnType<T>` 泛型。
例如:
``ts
const fn = (v: boolean) => {
if (v)
return 1
else
return 2
}
type a = MyReturnType<typeof fn> // 应推导出 "1 | 2"
``
> 在 Github 上查看:https://tsch.js.org/2/zh-CN
*/
/* _____________ 你的代码 _____________ */
type MyReturnType<T> = T extends (...args: any) => infer V ? V : never;
/* _____________ 测试用例 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<string, MyReturnType<() => string>>>,
Expect<Equal<123, MyReturnType<() => 123>>>,
Expect<Equal<ComplexObject, MyReturnType<() => ComplexObject>>>,
Expect<Equal<Promise<boolean>, MyReturnType<() => Promise<boolean>>>>,
Expect<Equal<() => 'foo', MyReturnType<() => () => 'foo'>>>,
Expect<Equal<1 | 2, MyReturnType<typeof fn>>>,
Expect<Equal<1 | 2, MyReturnType<typeof fn1>>>,
]
type ComplexObject = {
a: [12, 'foo']
bar: 'hello'
prev(): number
}
const fn = (v: boolean) => v ? 1 : 2
const fn1 = (v: boolean, w: any) => v ? 1 : 2
/* _____________ 下一步 _____________ */
/*
> 分享你的解答:https://tsch.js.org/2/answer/zh-CN
> 查看解答:https://tsch.js.org/2/solutions
> 更多题目:https://tsch.js.org/zh-CN
*/
实现 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,
}
解答:
/*
3 - 实现 Omit
-------
by Anthony Fu (@antfu) #中等 #union #built-in
### 题目
不使用 `Omit` 实现 TypeScript 的 `Omit<T, K>` 泛型。
`Omit` 会创建一个省略 `K` 中字段的 `T` 对象。
例如:
``ts
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyOmit<Todo, 'description' | 'title'>
const todo: TodoPreview = {
completed: false,
}
``
> 在 Github 上查看:https://tsch.js.org/3/zh-CN
*/
/* _____________ 你的代码 _____________ */
type MyOmit<T, K extends keyof T> = {
[P in keyof T as (P extends K ? never : P)]: T[P];
}
/* _____________ 测试用例 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<Expected1, MyOmit<Todo, 'description'>>>,
Expect<Equal<Expected2, MyOmit<Todo, 'description' | 'completed'>>>,
]
// @ts-expect-error
type error = MyOmit<Todo, 'description' | 'invalid'>
interface Todo {
title: string
description: string
completed: boolean
}
interface Expected1 {
title: string
completed: boolean
}
interface Expected2 {
title: string
}
/* _____________ 下一步 _____________ */
/*
> 分享你的解答:https://tsch.js.org/3/answer/zh-CN
> 查看解答:https://tsch.js.org/3/solutions
> 更多题目:https://tsch.js.org/zh-CN
*/
Readonly
实现一个通用 MyReadonly2<T, K>
,它带有两种类型的参数 T
和 K
。
K
指定应设置为 Readonly
的 T
的属性集。如果未提供 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
解答:
/*
8 - Readonly 2
-------
by Anthony Fu (@antfu) #中等 #readonly #object-keys
### 题目
> 由谷歌自动翻译,欢迎 PR 改进翻译质量。
实现一个通用`MyReadonly2<T, K>`,它带有两种类型的参数`T`和`K`。
`K`指定应设置为Readonly的`T`的属性集。如果未提供`K`,则应使所有属性都变为只读,就像普通的`Readonly<T>`一样。
例如
``ts
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
``
> 在 Github 上查看:https://tsch.js.org/8/zh-CN
*/
/* _____________ 你的代码 _____________ */
// 因为第二个泛型可能为空,所以需要通过 = 来赋默认值
// 因为第二个交叉类型使用了 (P extends K ? never : P) 的判断,所以 K 的默认值应该是 keyof T
type MyReadonly2<T, K extends keyof T = keyof T> = {
readonly [P in K]: T[P];
} & {
[P in keyof T as (P extends K ? never : P)]: T[P];
}
type t = MyReadonly2<Todo1>
/* _____________ 测试用例 _____________ */
import type { Alike, Expect } from '@type-challenges/utils'
type cases = [
Expect<Alike<MyReadonly2<Todo1>, Readonly<Todo1>>>,
Expect<Alike<MyReadonly2<Todo1, 'title' | 'description'>, Expected>>,
Expect<Alike<MyReadonly2<Todo2, 'title' | 'description'>, Expected>>,
Expect<Alike<MyReadonly2<Todo2, 'description' >, Expected>>,
]
// @ts-expect-error
type error = MyReadonly2<Todo1, 'title' | 'invalid'>
interface Todo1 {
title: string
description?: string
completed: boolean
}
interface Todo2 {
readonly title: string
description?: string
completed: boolean
}
interface Expected {
readonly title: string
readonly description?: string
completed: boolean
}
/* _____________ 下一步 _____________ */
/*
> 分享你的解答:https://tsch.js.org/8/answer/zh-CN
> 查看解答:https://tsch.js.org/8/solutions
> 更多题目:https://tsch.js.org/zh-CN
*/
深度 Readonly
实现一个通用的 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`
解答:
/*
9 - 深度 Readonly
-------
by Anthony Fu (@antfu) #中等 #readonly #object-keys #deep
### 题目
> 由谷歌自动翻译,欢迎 PR 改进翻译质量。
实现一个通用的`DeepReadonly<T>`,它将对象的每个参数及其子对象递归地设为只读。
您可以假设在此挑战中我们仅处理对象。数组,函数,类等都无需考虑。但是,您仍然可以通过覆盖尽可能多的不同案例来挑战自己。
例如
``ts
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`
``
> 在 Github 上查看:https://tsch.js.org/9/zh-CN
*/
/* _____________ 你的代码 _____________ */
// 是否是一个 不是函数 也 不是数组 的 对象
type IsPlainObject<T> = T extends object ?
T extends Function ? false :
T extends Array<any> ? false :
true :
false
type DeepReadonlyArray<T extends any[]> = {
[K in keyof T]: IsPlainObject<T[K]> extends true ? DeepReadonly<T[K]> : T[K];
}
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends [...infer Values] ?
readonly [...DeepReadonlyArray<Values>] :
IsPlainObject<T[K]> extends true ?
DeepReadonly<T[K]> :
T[K]
}
/* _____________ 测试用例 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<DeepReadonly<X1>, Expected1>>,
Expect<Equal<DeepReadonly<X2>, Expected2>>,
]
type X1 = {
a: () => 22
b: string
c: {
d: boolean
e: {
g: {
h: {
i: true
j: 'string'
}
k: 'hello'
}
l: [
'hi',
{
m: ['hey']
},
]
}
}
}
type X2 = { a: string } | { b: number }
type Expected1 = {
readonly a: () => 22
readonly b: string
readonly c: {
readonly d: boolean
readonly e: {
readonly g: {
readonly h: {
readonly i: true
readonly j: 'string'
}
readonly k: 'hello'
}
readonly l: readonly [
'hi',
{
readonly m: readonly ['hey']
},
]
}
}
}
type Expected2 = { readonly a: string } | { readonly b: number }
/* _____________ 下一步 _____________ */
/*
> 分享你的解答:https://tsch.js.org/9/answer/zh-CN
> 查看解答:https://tsch.js.org/9/solutions
> 更多题目:https://tsch.js.org/zh-CN
*/
元组转合集
实现泛型 TupleToUnion<T>
,它返回元组所有值的合集。
例如:
type Arr = ['1', '2', '3']
type Test = TupleToUnion<Arr> // expected to be '1' | '2' | '3'
解答:
/*
10 - 元组转合集
-------
by Anthony Fu (@antfu) #中等 #infer #tuple #union
### 题目
实现泛型`TupleToUnion<T>`,它返回元组所有值的合集。
例如
``ts
type Arr = ['1', '2', '3']
type Test = TupleToUnion<Arr> // expected to be '1' | '2' | '3'
``
> 在 Github 上查看:https://tsch.js.org/10/zh-CN
*/
/* _____________ 你的代码 _____________ */
type TupleToUnion<T extends any[]> = T[number];
/* _____________ 测试用例 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<TupleToUnion<[123, '456', true]>, 123 | '456' | true>>,
Expect<Equal<TupleToUnion<[123]>, 123>>,
]
/* _____________ 下一步 _____________ */
/*
> 分享你的解答:https://tsch.js.org/10/answer/zh-CN
> 查看解答:https://tsch.js.org/10/solutions
> 更多题目:https://tsch.js.org/zh-CN
*/
可串联构造器
在 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
}
}
解答:
/*
12 - 可串联构造器
-------
by Anthony Fu (@antfu) #中等 #application
### 题目
在 JavaScript 中我们经常会使用可串联(Chainable/Pipeline)的函数构造一个对象,但在 TypeScript 中,你能合理的给它赋上类型吗?
在这个挑战中,你可以使用任意你喜欢的方式实现这个类型 - Interface, Type 或 Class 都行。你需要提供两个函数 `option(key, value)` 和 `get()`。在 `option` 中你需要使用提供的 key 和 value 扩展当前的对象类型,通过 `get` 获取最终结果。
例如
``ts
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
}
}
``
你只需要在类型层面实现这个功能 - 不需要实现任何 TS/JS 的实际逻辑。
你可以假设 `key` 只接受字符串而 `value` 接受任何类型,你只需要暴露它传递的类型而不需要进行任何处理。同样的 `key` 只会被使用一次。
> 在 Github 上查看:https://tsch.js.org/12/zh-CN
*/
/* _____________ 你的代码 _____________ */
// 1. 可以使用 T = {} 来作为默认值,甚至默认参数与默认返回值,再通过递归传递 T 即可实现递归全局记录
// 2. option 是一个函数接收两个值:K 和 V,为了约束 key 不可重复必须范型传入,value 是任意类型范型不做约束直接透传
// 3. 验证重复 key,实现传入相同 key 报错
// 4. 直接 & 联合并不能将相同 key 的类型覆盖,因此用 Omit 去掉前一个类型中相同的 key
type Chainable<T = {}> = {
option<K extends string, V>(key: K extends keyof T ? never : K, value: V): Chainable<Omit<T, K> & Record<K, V>>
get(): T
}
/* _____________ 测试用例 _____________ */
import type { Alike, Expect } from '@type-challenges/utils'
declare const a: Chainable
const result1 = a
.option('foo', 123)
.option('bar', { value: 'Hello World' })
.option('name', 'type-challenges')
.get()
const result2 = a
.option('name', 'another name')
// @ts-expect-error
.option('name', 'last name')
.get()
const result3 = a
.option('name', 'another name')
// @ts-expect-error
.option('name', 123)
.get()
type cases = [
Expect<Alike<typeof result1, Expected1>>,
Expect<Alike<typeof result2, Expected2>>,
Expect<Alike<typeof result3, Expected3>>,
]
type Expected1 = {
foo: number
bar: {
value: string
}
name: string
}
type Expected2 = {
name: string
}
type Expected3 = {
name: number
}
/* _____________ 下一步 _____________ */
/*
> 分享你的解答:https://tsch.js.org/12/answer/zh-CN
> 查看解答:https://tsch.js.org/12/solutions
> 更多题目:https://tsch.js.org/zh-CN
*/
最后一个元素
实现一个通用 Last<T>
,它接受一个数组 T
并返回其最后一个元素的类型。
例如:
type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]
type tail1 = Last<arr1> // expected to be 'c'
type tail2 = Last<arr2> // expected to be 1
解答:
/*
15 - 最后一个元素
-------
by Anthony Fu (@antfu) #中等 #array
### 题目
> 由谷歌自动翻译,欢迎 PR 改进翻译质量。
>在此挑战中建议使用TypeScript 4.0
实现一个通用`Last<T>`,它接受一个数组`T`并返回其最后一个元素的类型。
例如
``ts
type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]
type tail1 = Last<arr1> // expected to be 'c'
type tail2 = Last<arr2> // expected to be 1
``
> 在 Github 上查看:https://tsch.js.org/15/zh-CN
*/
/* _____________ 你的代码 _____________ */
type Last<T extends any[]> = T extends [...infer F, infer LastValue] ? LastValue : never;
/* _____________ 测试用例 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<Last<[3, 2, 1]>, 1>>,
Expect<Equal<Last<[() => 123, { a: string }]>, { a: string }>>,
]
/* _____________ 下一步 _____________ */
/*
> 分享你的解答:https://tsch.js.org/15/answer/zh-CN
> 查看解答:https://tsch.js.org/15/solutions
> 更多题目:https://tsch.js.org/zh-CN
*/
出堆
实现一个通用 Pop<T>
,它接受一个数组 T
,并返回一个由数组 T
的前 length-1
项以相同的顺序组成的数组。
例如:
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]
解答:
/*
16 - 出堆
-------
by Anthony Fu (@antfu) #中等 #array
### 题目
>在此挑战中建议使用TypeScript 4.0
实现一个通用`Pop<T>`,它接受一个数组`T`,并返回一个由数组`T`的前length-1项以相同的顺序组成的数组。
例如
``ts
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]
``
**额外**:同样,您也可以实现`Shift`,`Push`和`Unshift`吗?
> 在 Github 上查看:https://tsch.js.org/16/zh-CN
*/
/* _____________ 你的代码 _____________ */
type Pop<T extends any[]> = T extends [] ? [] : T extends [...infer Before, infer Last] ? Before : never;
/* _____________ 测试用例 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<Pop<[3, 2, 1]>, [3, 2]>>,
Expect<Equal<Pop<['a', 'b', 'c', 'd']>, ['a', 'b', 'c']>>,
Expect<Equal<Pop<[]>, []>>,
]
/* _____________ 下一步 _____________ */
/*
> 分享你的解答:https://tsch.js.org/16/answer/zh-CN
> 查看解答:https://tsch.js.org/16/solutions
> 更多题目:https://tsch.js.org/zh-CN
*/
Promise.all
/*
20 - Promise.all
-------
by Anthony Fu (@antfu) #中等 #array #promise
### 题目
给函数`PromiseAll`指定类型,它接受元素为 Promise 或者类似 Promise 的对象的数组,返回值应为`Promise<T>`,其中`T`是这些 Promise 的结果组成的数组。
``ts
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)
``
> 在 Github 上查看:https://tsch.js.org/20/zh-CN
*/
/* _____________ 你的代码 _____________ */
declare function PromiseAll<T extends readonly any[]>(
values: readonly [...T],
): Promise<{
[K in keyof T]: Awaited<T[K]>;
}>;
/* _____________ 测试用例 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
const promiseAllTest1 = PromiseAll([1, 2, 3] as const)
const promiseAllTest2 = PromiseAll([1, 2, Promise.resolve(3)] as const)
const promiseAllTest3 = PromiseAll([1, 2, Promise.resolve(3)])
const promiseAllTest4 = PromiseAll<Array<number | Promise<number>>>([1, 2, 3])
type cases = [
Expect<Equal<typeof promiseAllTest1, Promise<[1, 2, 3]>>>,
Expect<Equal<typeof promiseAllTest2, Promise<[1, 2, number]>>>,
Expect<Equal<typeof promiseAllTest3, Promise<[number, number, number]>>>,
Expect<Equal<typeof promiseAllTest4, Promise<number[]>>>,
]
/* _____________ 下一步 _____________ */
/*
> 分享你的解答:https://tsch.js.org/20/answer/zh-CN
> 查看解答:https://tsch.js.org/20/solutions
> 更多题目:https://tsch.js.org/zh-CN
*/
Type Lookup
有时,您可能希望根据某个属性在联合类型中查找类型。
在此挑战中,我们想通过在联合类型 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`
解答:
// ============= Test Cases =============
import type { Equal, Expect } from './test-utils';
interface Cat {
type: 'cat';
breeds: 'Abyssinian' | 'Shorthair' | 'Curl' | 'Bengal';
}
interface Dog {
type: 'dog';
breeds: 'Hound' | 'Brittany' | 'Bulldog' | 'Boxer';
color: 'brown' | 'white' | 'black';
}
type Animal = Cat | Dog;
type cases = [Expect<Equal<LookUp<Animal, 'dog'>, Dog>>, Expect<Equal<LookUp<Animal, 'cat'>, Cat>>];
// ============= Your Code Here =============
type LookUp<U, T> = U extends { type: T } ? U : never;
Trim Left
实现 TrimLeft<T>
,它接收确定的字符串类型并返回一个新的字符串,其中新返回的字符串删除了原字符串开头的空白字符串。
例如:
type trimed = TrimLeft<' Hello World '> // 应推导出 'Hello World '
解答:
// ============= Test Cases =============
import type { Equal, Expect } from './test-utils';
type cases = [
Expect<Equal<TrimLeft<'str'>, 'str'>>,
Expect<Equal<TrimLeft<' str'>, 'str'>>,
Expect<Equal<TrimLeft<' str'>, 'str'>>,
Expect<Equal<TrimLeft<' str '>, 'str '>>,
Expect<Equal<TrimLeft<' \n\t foo bar '>, 'foo bar '>>,
Expect<Equal<TrimLeft<''>, ''>>,
Expect<Equal<TrimLeft<' \n\t'>, ''>>,
];
// ============= Your Code Here =============
type Space = ' ' | '\n' | '\t';
type TrimLeft<S extends string> = S extends `${Space}${infer Rest}` ? TrimLeft<Rest> : S;
Trim
实现 Trim<T>
,它是一个字符串类型,并返回一个新字符串,其中两端的空白符都已被删除。
例如:
type trimed = Trim<' Hello World '> // expected to be 'Hello World'
解答:
// ============= Test Cases =============
import type { Equal, Expect } from './test-utils';
type cases = [
Expect<Equal<Trim<'str'>, 'str'>>,
Expect<Equal<Trim<' str'>, 'str'>>,
Expect<Equal<Trim<' str'>, 'str'>>,
Expect<Equal<Trim<'str '>, 'str'>>,
Expect<Equal<Trim<' str '>, 'str'>>,
Expect<Equal<Trim<' \n\t foo bar \t'>, 'foo bar'>>,
Expect<Equal<Trim<''>, ''>>,
Expect<Equal<Trim<' \n\t '>, ''>>,
];
// ============= Your Code Here =============
type Space = ' ' | '\t' | '\n';
type Trim<S extends string> = S extends `${Space}${infer R}` | `${infer R}${Space}` ? Trim<R> : S;
Capitalize
实现 Capitalize<T>
,它将字符串的首字母转换为大写。
例如:
type capitalized = Capitalize<'hello world'> // expected to be 'Hello world'
解答:
/* _____________ 你的代码 _____________ */
type MyCapitalize<S extends string> = S extends `${infer F}${infer R}` ? `${Uppercase<F>}${R}` : S;
/* _____________ 测试用例 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<MyCapitalize<'foobar'>, 'Foobar'>>,
Expect<Equal<MyCapitalize<'FOOBAR'>, 'FOOBAR'>>,
Expect<Equal<MyCapitalize<'foo bar'>, 'Foo bar'>>,
Expect<Equal<MyCapitalize<''>, ''>>,
Expect<Equal<MyCapitalize<'a'>, 'A'>>,
Expect<Equal<MyCapitalize<'b'>, 'B'>>,
Expect<Equal<MyCapitalize<'c'>, 'C'>>,
Expect<Equal<MyCapitalize<'d'>, 'D'>>,
Expect<Equal<MyCapitalize<'e'>, 'E'>>,
Expect<Equal<MyCapitalize<'f'>, 'F'>>,
Expect<Equal<MyCapitalize<'g'>, 'G'>>,
Expect<Equal<MyCapitalize<'h'>, 'H'>>,
Expect<Equal<MyCapitalize<'i'>, 'I'>>,
Expect<Equal<MyCapitalize<'j'>, 'J'>>,
Expect<Equal<MyCapitalize<'k'>, 'K'>>,
Expect<Equal<MyCapitalize<'l'>, 'L'>>,
Expect<Equal<MyCapitalize<'m'>, 'M'>>,
Expect<Equal<MyCapitalize<'n'>, 'N'>>,
Expect<Equal<MyCapitalize<'o'>, 'O'>>,
Expect<Equal<MyCapitalize<'p'>, 'P'>>,
Expect<Equal<MyCapitalize<'q'>, 'Q'>>,
Expect<Equal<MyCapitalize<'r'>, 'R'>>,
Expect<Equal<MyCapitalize<'s'>, 'S'>>,
Expect<Equal<MyCapitalize<'t'>, 'T'>>,
Expect<Equal<MyCapitalize<'u'>, 'U'>>,
Expect<Equal<MyCapitalize<'v'>, 'V'>>,
Expect<Equal<MyCapitalize<'w'>, 'W'>>,
Expect<Equal<MyCapitalize<'x'>, 'X'>>,
Expect<Equal<MyCapitalize<'y'>, 'Y'>>,
Expect<Equal<MyCapitalize<'z'>, 'Z'>>,
]
Replace
实现 Replace<S, From, To>
将字符串 S
中的第一个子字符串 From
替换为 To
。
例如:
type replaced = Replace<'types are fun!', 'fun', 'awesome'> // 期望是 'types are awesome!'
解答:
/* _____________ 你的代码 _____________ */
type Replace<S extends string, From extends string, To extends string> = From extends '' ? S : S extends `${infer L}${From}${infer R}` ? `${L}${To}${R}` : S
/* _____________ 测试用例 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<Replace<'foobar', 'bar', 'foo'>, 'foofoo'>>,
Expect<Equal<Replace<'foobarbar', 'bar', 'foo'>, 'foofoobar'>>,
Expect<Equal<Replace<'foobarbar', '', 'foo'>, 'foobarbar'>>,
Expect<Equal<Replace<'foobarbar', 'bar', ''>, 'foobar'>>,
Expect<Equal<Replace<'foobarbar', 'bra', 'foo'>, 'foobarbar'>>,
Expect<Equal<Replace<'', '', ''>, ''>>,
]
ReplaceAll
实现 ReplaceAll<S, From, To>
将字符串 S
中的所有子字符串 From
替换为 To
。
例如:
type replaced = ReplaceAll<'t y p e s', ' ', ''> // 期望是 'types'
解答:
/* _____________ 你的代码 _____________ */
type ReplaceAll<S extends string, From extends string, To extends string> = From extends '' ? S : S extends `${infer L}${From}${infer R}` ? `${L}${To}${ReplaceAll<R, From, To>}` : S
/* _____________ 测试用例 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<ReplaceAll<'foobar', 'bar', 'foo'>, 'foofoo'>>,
Expect<Equal<ReplaceAll<'foobar', 'bag', 'foo'>, 'foobar'>>,
Expect<Equal<ReplaceAll<'foobarbar', 'bar', 'foo'>, 'foofoofoo'>>,
Expect<Equal<ReplaceAll<'t y p e s', ' ', ''>, 'types'>>,
Expect<Equal<ReplaceAll<'foobarbar', '', 'foo'>, 'foobarbar'>>,
Expect<Equal<ReplaceAll<'barfoo', 'bar', 'foo'>, 'foofoo'>>,
Expect<Equal<ReplaceAll<'foobarfoobar', 'ob', 'b'>, 'fobarfobar'>>,
Expect<Equal<ReplaceAll<'foboorfoboar', 'bo', 'b'>, 'foborfobar'>>,
Expect<Equal<ReplaceAll<'', '', ''>, ''>>,
]
Append Argument
实现一个泛型 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
解答:
// ============= Test Cases =============
import type { Equal, Expect } from './test-utils';
type Case1 = AppendArgument<(a: number, b: string) => number, boolean>;
type Result1 = (a: number, b: string, x: boolean) => number;
type Case2 = AppendArgument<() => void, undefined>;
type Result2 = (x: undefined) => void;
type cases = [
Expect<Equal<Case1, Result1>>,
Expect<Equal<Case2, Result2>>,
// @ts-expect-error
AppendArgument<unknown, undefined>,
];
// ============= Your Code Here =============
type AppendArgument<Fn extends (...args: any[]) => unknown, A> = Fn extends (
...args: infer R
) => infer V
? (...args: [...R, A]) => V
: never;
Permutation
实现联合类型的全排列,将联合类型转换成所有可能的全排列数组的联合类型。
示例:
type perm = Permutation<'A' | 'B' | 'C'>;
// ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']
解答:
// ============= Test Cases =============
import type { Equal, Expect } from './test-utils';
type cases = [
Expect<Equal<Permutation<'A'>, ['A']>>,
Expect<
Equal<
Permutation<'A' | 'B' | 'C'>,
| ['A', 'B', 'C']
| ['A', 'C', 'B']
| ['B', 'A', 'C']
| ['B', 'C', 'A']
| ['C', 'A', 'B']
| ['C', 'B', 'A']
>
>,
Expect<
Equal<
Permutation<'B' | 'A' | 'C'>,
| ['A', 'B', 'C']
| ['A', 'C', 'B']
| ['B', 'A', 'C']
| ['B', 'C', 'A']
| ['C', 'A', 'B']
| ['C', 'B', 'A']
>
>,
Expect<Equal<Permutation<boolean>, [false, true] | [true, false]>>,
Expect<Equal<Permutation<never>, []>>,
];
// ============= Your Code Here =============
// 方案一:
// type Permutation<T, K = T> = [T] extends [never]
// ? []
// : T extends infer P
// ? [P, ...Permutation<Exclude<K, P>>]
// : [];
// 方案二:
type Permutation<T, K = T> = [T] extends [never]
? []
: K extends K
? [K, ...Permutation<Exclude<T, K>>]
: [];
Length of String
计算字符串的长度,类似于 String#length
。
解答:
// ============= Test Cases =============
import type { Equal, Expect } from './test-utils';
type cases = [
Expect<Equal<LengthOfString<''>, 0>>,
Expect<Equal<LengthOfString<'kumiko'>, 6>>,
Expect<Equal<LengthOfString<'reina'>, 5>>,
Expect<Equal<LengthOfString<'Sound! Euphonium'>, 16>>,
];
// ============= Your Code Here =============
type LengthOfString<S extends string, T extends string[] = []> = S extends `${infer F}${infer R}`
? LengthOfString<R, [F, ...T]>
: T['length'];