Open ForeveHG opened 2 years ago
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
type MyReadonly<T> = {
readonly [key in keyof T]: T[key]
}
/* _____________ 测试用例 _____________ */
import { 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
}
}
传入一个元组类型,将这个元组类型转换为对象类型,这个对象类型的键/值都是从元组中遍历出来。
例如:
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'}
type TupleToObject<T extends readonly string[]> = {
[key in T[number]]: key
}
in 操作符: 遍历联合类型 T[number]: 元祖转联合类型
实现泛型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 (infer U)[] ? U : never
//或者
type TupleToUnion<T> = T extends Array<infer U> ? U : never
给一个只包含string类型的元祖T
和一个类型U
,实现一个嵌套对象
type a = TupleToNestedObject<['a'], string> // {a: string}
type b = TupleToNestedObject<['a', 'b'], number> // {a: {b: number}}
type c = TupleToNestedObject<[], boolean> // boolean. if the tuple is empty, just return the U type
type TupleToNestedObject<T,U> = T extends [infer First, ...infer Rest] ? First extends string ? {
[key in First]: TupleToNestedObject<Rest, U>
} : never : U
/ 测试用例 / import { Equal, Expect, ExpectFalse, NotEqual } from '@type-challenges/utils'
type cases = [ Expect<Equal<TupleToNestedObject<['a'], string>, {a: string}>>, Expect<Equal<TupleToNestedObject<['a', 'b'], number>, {a: {b: number}}>>, Expect<Equal<TupleToNestedObject<['a', 'b', 'c'], boolean>, {a: {b: {c: boolean}}}>>, Expect<Equal<TupleToNestedObject<[], boolean>, boolean>>, ]
/ Further Steps / /*
Share your solutions: https://tsch.js.org/3188/answer View solutions: https://tsch.js.org/3188/solutions More Challenges: https://tsch.js.org */
从联合类型T中排除U的类型成员,来构造一个新的类型。
type Exclude<T,U> = T extends U ? never : T ;
/ 测试用例 / import { Equal, Expect } from '@type-challenges/utils'
type cases = [ Expect<Equal<MyExclude<"a" | "b" | "c", "a">, Exclude<"a" | "b" | "c", "a">>>, Expect<Equal<MyExclude<"a" | "b" | "c", "a" | "b">, Exclude<"a" | "b" | "c", "a" | "b">>>, Expect<Equal<MyExclude<string | number | (() => void), Function>, Exclude<string | number | (() => void), Function>>>, ]
创建一个通用的
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
type Length<T extends readonly any[]> = T['length']
/ 测试用例 / import { 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
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'
type If<C extends boolean, T, F> = C extends true ? T : F;
/ 测试用例 / import { 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'>
在类型系统里实现 JavaScript 内置的
Array.concat
方法,这个类型接受两个参数,返回的新数组类型应该按照输入参数从左到右的顺序合并为一个新的数组。
举例:
type Result = Concat<[1], [2]> // expected to be [1, 2]
type Concat<T extends any[], U extends any[]> = [...T, ...U]
import { 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']>>,
]
在类型系统里实现 JavaScript 的
Array.includes
方法,这个类型接受两个参数,返回的类型要么是true
要么是false
。
举例:
type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`
type Includes<T extends readonly any[], U> = T extends [infer First, ...infer Rest] ? [First, U] extends [U, First] ? true : Includes<Rest, U> : false
该实现不能满足测试用例,因为extends会忽略readonly等修饰符:
Expect<Equal<Includes<[{ a: 'A' }], { readonly a: 'A' }>, false>>,
Expect<Equal<Includes<[{ readonly a: 'A' }], { a: 'A' }>, false>>,
另外一种实现方式:
type Includes<T extends readonly any[], U> = T extends [infer First, ...infer Rest] ? IsEqual<First, U> extends true ? true : Includes<Rest, U> : false
可以满足所有测试用例,但目前不太明白原理,参考 https://stackoverflow.com/questions/68961864/how-does-the-equals-work-in-typescript/68963796#68963796,暂时理解为: ts条件类型(extends)中泛型的可分配性的规则要求,extends后的类型要与类型检查器(isTypeIdenticalTo)的定义相同
declare let x: <T>() => (T extends {a: string} ? 1 : 2)
declare let y: <T>() => (T extends {readonly a: string} ? 1 : 2)
y = x // error
/ 测试用例 / import { 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>>, ]
在类型系统里实现通用的 Array.push
。
举例如下,
type Result = Push<[1, 2], '3'> // [1, 2, '3']
type Push<T extends any[], U> = [...T, U];
/* _____________ 测试用例 _____________ */
import { Equal, Expect, ExpectFalse, NotEqual } 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]>>,
]
实现类型版本的 Array.unshift
。
举例,
type Result = Unshift<[1, 2], 0> // [0, 1, 2,]
type Push<T extends any[], U> = [U, ...T];
/* _____________ 测试用例 _____________ */
import { Equal, Expect, ExpectFalse, NotEqual } 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']>>,
]
实现内置的 Parameters
类型,而不是直接使用它,可参考TypeScript官方文档。
type MyParameters<T extends (...args: any[]) => any> = T extends (...x: infer U) => any ? U : never
/* _____________ 测试用例 _____________ */
import { Equal, Expect, ExpectFalse, NotEqual } 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
*/
假如我们有一个 Promise 对象,这个 Promise 对象会返回一个类型。在 TS 中,我们用 Promise
中的 T 来描述这个 Promise 返回的类型。请你实现一个类型,可以获取这个类型。
比如:Promise<ExampleType>
,请你返回 ExampleType 类型。
这个挑战来自于 @maciejsikora 的文章:original article
在 Github 上查看:https://tsch.js.org/189/zh-CN
// type MyAwaited<T> = T extends Promise<infer U> ? U extends Promise<infer Z> ? MyAwaited<Promise<Z>> : U : never
type MyAwaited<T> = T extends Promise<infer I>? MyAwaited<I> : T
/* _____________ 测试用例 _____________ */
import { Equal, Expect } from '@type-challenges/utils'
type X = Promise<string>
type Y = Promise<{ field: number }>
type Z = Promise<Promise<string | number>>
type cases = [
Expect<Equal<MyAwaited<X>, string>>,
Expect<Equal<MyAwaited<Y>, { field: number }>>,
Expect<Equal<MyAwaited<Z>, string | number>>,
]
// @ts-expect-error
type error = MyAwaited<number>
不使用 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,
}
type MyOmit<T, K> = {
[key in Exclude<keyof T,K>]: T[key]
}
Implement a generic
TupleToUnion<T>
which covers the values of a tuple to its values union.
type Arr = ['1', '2', '3']
type Test = TupleToUnion<Arr> // expected to be '1' | '2' | '3'
type TupleToUnion<T extends any[]> = T[number]
/* _____________ Test Cases _____________ */
import { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<TupleToUnion<[123, '456', true]>, 123 | '456' | true>>,
Expect<Equal<TupleToUnion<[123]>, 123>>,
]
实现一个通用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
type Last<T extends any[]> = T extends [...infer Rest, infer Last] ? Last : never
/* _____________ 测试用例 _____________ */
import { 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
*/
在 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
}
}
你只需要在类型层面实现这个功能 - 不需要实现任何 TS/JS 的实际逻辑。
你可以假设 key
只接受字符串而 value
接受任何类型,你只需要暴露它传递的类型而不需要进行任何处理。同样的 key
只会被使用一次。
// type Chainable<T = {}> = {
// option<K extends string, U>(key: K, value: U): Chainable<{[key in K]: U} & T>
// get(): T
// }
// abstract class Chainable<T = {}> {
// abstract option<K extends string, U>(key: K, value:U):Chainable<{[key in K]: U} & T>
// abstract get():T
// }
interface Chainable<T = {}> {
option<K extends string, U>(key: K, value: U): Chainable<{[key in K]: U} & T>
get(): T
}
功能实现:
interface Chainable<T = {}> {
option<K extends string, U>(key: K, value: U): Chainable<{[key in K]?: U} & T>
get(): T
}
let obj:{[key:string]: any} = {}
let C: Chainable = {
option(key:string, value:any) {
obj[key] = value
return C
},
get() {
return obj
}
}
let c = C.option('age', 12).option('name', 'jiali').get()
/* _____________ 测试用例 _____________ */
import { Alike, Expect } from '@type-challenges/utils'
declare const a: Chainable
const result = a
.option('foo', 123)
.option('bar', { value: 'Hello World' })
.option('name', 'type-challenges')
.get()
typeof result
type cases = [
Expect<Alike<typeof result, Expected>>
]
type Expected = {
foo: number
bar: {
value: string
}
name: string
}
/* _____________ 下一步 _____________ */
/*
> 分享你的解答:https://tsch.js.org/12/answer/zh-CN
> 查看解答:https://tsch.js.org/12/solutions
> 更多题目:https://tsch.js.org/zh-CN
*/
有时,您可能希望根据其属性在并集中查找类型。
在此挑战中,我们想通过在联合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 {type: T} ? U : never;
/* _____________ 测试用例 _____________ */
import { Equal, Expect } from '@type-challenges/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>>,
]
/* _____________ 下一步 _____________ */
/*
> 分享你的解答:https://tsch.js.org/62/answer/zh-CN
> 查看解答:https://tsch.js.org/62/solutions
> 更多题目:https://tsch.js.org/zh-CN
*/
题目
实现 TS 内置的
Pick<T, K>
,但不可以使用它。从类型
T
中选择出属性K
,构造成一个新的类型。例如:
实现: