初衷:
github上很火的 Ts Type challenge系列,由于题目和答案分离,学习起来比较麻烦,所以在自己实践学习的过程中,把题目和答案整理在一起,并集合了每个题解的相关知识点。
适合人群
此文章适合有一定TS基础知识的人群,刚入门的那种就可以
Hellow World
// 期望是一个 string 类型
type HelloWorld = any
// 你需要使得如下这行不会抛出异常
type test = Expect<Equal<HelloWorld, string>>
// 答案
type HelloWorld = string
实现Pick
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyPick<Todo, 'title' | 'completed'>
const todo: TodoPreview = {
title: 'Clean room',
completed: false,
}
// 答案
type MyPick<T, K extends keyof T> = {
[key in K] : T[key]
}
相关知识点
keyof 返回类型是联合类型
interface Person {
name: string;
age: number;
location: string;
}
type K1 = keyof Person; // "name" | "age" | "location"
in 遍历属性
type Person = {
name: string;
age: number;
}
type TypeToNumber<T> = {
[key in keyof T]: number
}
const obj: TypeToNumber<Person> = { name: 10, age: 10 }
[ 自定义变量名 in 枚举类型 ]: 类型
extends 在泛型场景里,约束左边必须是右边的子类型
function prop(obj: object, key: string) {
return (obj as any)[key];
}
function prop<T extends object, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
// 定义 T 类型并使用 extends 关键字约束该类型必须是 object 类型的子类型
// 然后使用 keyof 操作符获取 T 类型的所有键,其返回类型是联合类型
// 最后利用 extends 关键字约束 K 类型必须为 keyof T 联合类型的子类型
实现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]
}
元组转换为对象
// 传入一个元组类型,将这个元组类型转换为对象类型,这个对象类型的键/值都是从元组中遍历出来
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const
// typeof tuple 值为元祖类型
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 any[]> = {
[k in T[number]]: k
}
相关知识点
in 遍历元祖
const tuple = ['1', '2 ']
// T表示元祖 tuple
k in T[number] // k对应指 1,2
// T[number] 表示元祖对应Key的值
// T[number] === keyof T
const 断言
- 字面量类型使用const断言后,不能被修改
- 对象字面量的属性使用后,将被readonly修饰,变成只读
- 数组字面量变成readonly元祖
typeof
type Person = {
name:string
age:number
}
let man:Person = {
name:'han'
age:30
}
// 把man的type取出来,即Person
type Human = typeof man // Person
第一个元素 最后一个元素
实现一个通用First,它接受一个数组T并返回它的第一个元素的类型。
实现一个通用Last,它接受一个数组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
// 答案
// 把不存在的情况,空数组置为 never
type First<T extends any[]> = T extends [] ? never : T[0]
type Last<T extends any[]> = T extends [...any[], infer L] ? L : never
相关知识点
infer 表示在extends条件语句中待推断的类型变量
type ParamType<T> = T extends (arg: infer P) => any ? P : T
// infer P 表示待推断的函数参数
// 整句表示为:如果 T 能赋值给 (arg: infer P) => any,
// 则结果是 (arg: infer P) => any 类型中的参数 P,否则返回为 T
type ReturnType<T> = T extends (...args: any[]) => infer P ? P : any
// 用于提取函数类型的返回值类型
type Func = () => User;
type Test = ReturnType<Func>; // Test = User
获取元祖长度
// 创建一个通用的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']
实现Exclude
从联合类型T中排除U的类型成员,来构造一个新的类型
type Result = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'
// 答案
// extends 作为条件判断
// 如果extends前面的类型能够赋值给extends后面的类型,那么表达式判断为真,否则为假
type MyExclude<T,U> = T extends U ? never : T
相关知识点
extends
type A1 = 'x' extends 'x' ? string : number; // string
type A2 = 'x' | 'y' extends 'x' ? string : number; // number
type P<T> = T extends 'x' ? string : number;
type A3 = P<'x' | 'y'> // string | number
为啥上面A1 和 A3是不同的结果?
答:分配条件类型:对于使用extends关键字的条件类型(即上面的三元表达式类型),如果extends前面的参数是一个泛型类型,当传入该参数的是联合类型,则使用分配律计算最终的结果。分配律是指,将联合类型的联合项拆成单项,分别代入条件类型,然后将每个单项代入得到的结果再联合起来,得到最终的判断结果。
Includes
在类型系统里实现 JavaScript 的 Array.includes 方法,返回的类型要么是 true 要么是 false
type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'>
// expected to be `false`
// 答案
// 如果extends前面的类型能够赋值给extends后面的类型,那么表达式判断为真,否则为假
type Includes<T extends any[],U> = U extends T[number] ? true : false
Awaited
假如我们有一个 Promise 对象,这个 Promise 对象会返回一个类型。在 TS 中,我们用 Promise 中的 T 来描述这个 Promise 返回的类型。
请你实现一个类型,可以获取这个类型
type ExampleType = Promise<string>
type Result = MyAwaited<ExampleType> // string
// 答案
type MyAwaited<T> = T extends Promise<infer U> ? MyAwaited<U> : T
If
// 类似于三元判断 true则返回左边
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
Concat
type Result = Concat<[1], [2]> // expected to be [1, 2]
// 答案
type Concat<T extends any[],U extends any[]> = [...T,...U]
Push
type Result = Push<[1, 2], '3'> // [1, 2, '3']
// 答案
type Push<T extends any[],U> = [...T, U]
Unshift
type Result = Unshift<[1, 2], 0> // [0, 1, 2,]
// 答案
type Unshift<T extends any[],U> = [U,...T]
Parameters
const foo = (arg1: string, arg2: number): void => {}
type FunctionParamsType = MyParameters<typeof foo>
// [arg1: string, arg2: number]
// 答案
type Func = (...args: any[]) => any
type MyParameters<T extends Func> = T extends (...args: infer R) => any ? R : never