玩转 ts 类型体操!type-challenges easy篇 🎉🎉

type-challenges 是一个typescript​​类型体操的解题开源项目,可以让你像刷leetcode​​使用typescript​​来实现一些功能。我们所熟知的typescript​​只是用来编写js的时候作为类型定义,其实typescript​​能做的事情远不止这些,它也可以同js一样,实现一些逻辑运算,而有些使用typescript​​实现功能的操作比较难以理解说,所以也被戏称为"类型体操"。

在type-challenges​​ 中一共有4种难度的题目,分别是:easy​​(简单)、medium​​(中等)、hard​​(难)、extreme​​(地狱)。

本篇文章会先实现easy​​类型的题目。

题目一:实现Pick

题目:

不使用 Pick<T, K>​​ ,实现 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,
}

解答:

其实这道题是模拟ts的内置工具类型:Pick​​。作用是从一个类型中挑出某些属性。

ts 复制代码
type MyPick<T, K extends keyof T> = {
  [k in K]: T[k]
}

​keyof​​ 是 ts 中的一种类型操作符,用于获取一个类型的所有键(属性名)作为联合类型。它的语法如下:

ts 复制代码
type Keys = keyof ObjType

其中:

  • keyof 是 TypeScript 的关键字,用于指定要获取键的类型操作。
  • ObjType 是想要获取键的对象类型。

举个🌰:

ts 复制代码
type ObjType = { name: string; age: number };

type Keys = keyof ObjType; 
// Keys =  "name" | "age"

首先第二个泛型K被约束为T类型所有键(属性名)的联合类型,也就是K类型必须为T类型的子集。

ts 复制代码
[k in K]: T[k]

in 语法在ts中作为遍历操作,挨个取出K类型的所有键,然后通过键获取对应在T类型中的值。所以MyPick​​用js的语法来看,类似于以下逻辑:

ts 复制代码
const obj = {
	name: '瓜',
	age: 18,
	phone: 123
}

const pickAry = ['name', 'age']
let newObj = {}

function myPick(obj, pickAry) {
	for(let i = 0; i < pickAry.length; i++) {
		let key = pickAry[i]
		newObj[key] = obj[key]
	}
}

题目二:对象属性只读

题目:

不要使用内置的Readonly​​,自己实现一个。

泛型 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

解答:

ts 复制代码
type MyReadonly<T> = {
  readonly [P in keyof T]: T[P];
};

同样是利用 in 操作符,将所有的对象中键进行遍历,然后通过 T 类型取值,只不过在前面加上readonly​​。

题目三:元组转换为对象

题目:

将一个元组类型转换为对象类型,这个对象类型的键/值和元组中的元素对应。

例如:

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'}

解答:

ts 复制代码
type TupleToObject<T extends readonly PropertyKey[]> = {
  [K in T[number]]: K
}

T 类型被约束为只读类型,直接用ts内置的类型PropertyKey​​,类型定义是:

ts 复制代码
type PropertyKey = string | number | symbol;

其实如果想限制基本类型,可以使用:

ts 复制代码
type Test = keyof any 
// Test = string | number | symbol

​keyof​​操作符是用于提取后面对象键的操作,那么keyof any​​,意思就是提取一个any类型的对象键,但是对象键只可能是 string​​ number​​ symbol​​。

题目四:第一个元素

题目:

实现一个First​​泛型,它接受一个数组T​​并返回它的第一个元素的类型。

例如:

ts 复制代码
type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]

type head1 = First<arr1> // 应推导出 'a'
type head2 = First<arr2> // 应推导出 3

解答:

​infer​​关键字可以获取指定的元素通过位置获取不同的值。

ts 复制代码
type First<T extends any[]> = T extends [infer P, ...any[]] ? P : never

获取最后一项,同时也可以通过 R 获取剩余项。

ts 复制代码
type First<T extends any[]> = T extends [...infer R, infer P] ? P : never

题目五:获取元组长度

题目:

创建一个Length​​泛型,这个泛型接受一个只读的元组,返回这个元组的长度。

例如:

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

解答:

ts 中同样可以通过length​​获取长度。

ts 复制代码
type Length<T extends readonly any[]> = T['length']

题目六:实现 Exclude

题目:

实现内置的 Exclude<T, U>​​ 类型,但不能直接使用它本身。

从联合类型 T​​ 中排除 U​​ 中的类型,来构造一个新的类型。

例如:

ts 复制代码
type Result = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'

解答:

T 类型的每一项都会与U类型比较,never​​会被忽略

r 复制代码
type MyExclude<T, U> =  T extends U ? never : T;
ts 复制代码
type A = MyExclude<'key1' | 'key2', 'key2'>
 
// 等价于
 
type A = MyExclude<'key1', 'key2'> | MyExclude<'key2', 'key2'>
 
// =>
 
type A = ('key1' extends 'key2' ? never : 'key1') | ('key2' extends 'key2' ? never : 'key2')
 
// =>
 
// never是所有类型的子类型
type A = 'key1' | never = 'key1'

题目七:Awaited

题目:

假如我们有一个 Promise 对象,这个 Promise 对象会返回一个类型。在 TS 中,我们用 Promise 中的 T 来描述这个 Promise 返回的类型。请你实现一个类型,可以获取这个类型。

例如:Promise​​,请你返回 ExampleType 类型。

ts 复制代码
type ExampleType = Promise<string>

type Result = MyAwaited<ExampleType> // string

解答:

具有 then​​ 方法的对象就是 PromiseLike​​。

ts 复制代码
type MyAwaited<T extends PromiseLike<any>> = T extends PromiseLike<infer U>
  ? U extends PromiseLike<any>
    ? MyAwaited<U>
    : U
  : never;

题目八:if

题目:

实现一个 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'

解答:

ts 复制代码
type If<C extends boolean, T, F> = C extends true ? T : F;

题目九:Concat

题目:

在类型系统里实现 JavaScript 内置的 Array.concat​​ 方法,这个类型接受两个参数,返回的新数组类型应该按照输入参数从左到右的顺序合并为一个新的数组。

例如:

ts 复制代码
type Result = Concat<[1], [2]> // expected to be [1, 2]

解答:

ts 复制代码
type Tuple = readonly unknown[];

type Concat<T extends Tuple, U extends Tuple> = [...T, ...U];

题目十:Includes

题目:

在类型系统里实现 JavaScript 的 Array.includes​​ 方法,这个类型接受两个参数,返回的类型要么是 true​​ 要么是 false​​。

例如:

ts 复制代码
type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`

解答:

在ts中同样可以实现递归的逻辑,通过调用自己,每次调用时取出其中一位与U进行比较。

ts 复制代码
type Includes<T extends readonly any[], U> = T extends [infer L, ...infer R]
  ? [U, L] extends [L, U]
    ? true
    : Includes<R, U>
  : false

题目十一:push

题目:

在类型系统里实现通用的 Array.push​​ 。

例如:

ts 复制代码
type Result = Push<[1, 2], '3'> // [1, 2, '3']

解答:

ts 复制代码
type Push<T extends unknown[], U> = [...T, U]

题目十二:Unshift

题目:

实现类型版本的 Array.unshift​​。

例如:

ts 复制代码
type Result = Unshift<[1, 2], 0> // [0, 1, 2,]

解答:

ts 复制代码
type Unshift<T extends unknown[], U> = [U, ...T]

题目十三:Parameters

题目:

实现内置的 Parameters 类型,而不是直接使用它,可参考TypeScript官方文档

例如:

ts 复制代码
const foo = (arg1: string, arg2: number): void => {}

type FunctionParamsType = MyParameters<typeof foo> // [arg1: string, arg2: number]

解答:

​infer​​ 同样可以获取函数的参数类型:

ts 复制代码
type MyParameters<T extends (...args: any[]) => any> = T extends (...any: infer S) => any ? S : any 

写在最后

未来可能会更新实现mini-reactantd源码解析系列,希望能一直坚持下去,期待多多点赞🤗🤗,一起进步!🥳🥳 ‍ ‍ ‍

相关推荐
嘤嘤怪呆呆狗9 分钟前
【插件】vscode Todo Tree 简介和使用方法
前端·ide·vue.js·vscode·编辑器
ᥬ 小月亮22 分钟前
Js前端模块化规范及其产品
开发语言·前端·javascript
码小瑞37 分钟前
某些iphone手机录音获取流stream延迟问题 以及 录音一次第二次不录音问题
前端·javascript·vue.js
weixin_18939 分钟前
‌Vite和Webpack区别 及 优劣势
前端·webpack·vue·vite
半吊子伯爵40 分钟前
开发过程优化·自定义鼠标右键菜单
前端·javascript·自定义鼠标右键菜单
xcLeigh43 分钟前
HTML5实现好看的喜庆圣诞节网站源码
前端·html·html5
Tirzano1 小时前
vue3 ts 简单动态表单 和表格
前端·javascript·vue.js
杰~JIE1 小时前
前端工程化概述(初版)
前端·自动化·工程化·前端工程化·sop
程序员_三木1 小时前
使用 Three.js 创建圣诞树场景
开发语言·前端·javascript·ecmascript·three
赵大仁2 小时前
深入理解 Vue 3 中的具名插槽
前端·javascript·vue.js·react.js·前端框架·ecmascript·html5