深入理解 TypeScript 工具函数:一起学习,一起进步!

前言

TypeScript (TS) 对于我们来说可能已经十分熟悉,然而,对于其内置的工具函数,我们在使用时可能并不完全理解其工作原理。为了深入理解并灵活运用这些工具函数,我们今天将一起探索和实现15个简单的 TypeScript 工具函数。

这不仅是一个学习的过程,也是一个共同提升,共同进步的旅程。让我们一起开始吧!

如果大家觉得本文对您有帮助,麻烦大家动动小手,帮土豆点个赞,谢谢大家了。

题目

一 实现 自定义 Pick

题目描述

不使用 Pick<T, K> ,实现 TS 内置的 Pick<T, K> 的功能。从类型 T 中选出符合 K 的属性,构造一个新的类型。

例如:

typescript 复制代码
interface Todo {
  title: string
  description: string
  completed: boolean
}

type TodoPreview = MyPick<Todo, 'title' | 'completed'>

const todo: TodoPreview = {
    title: 'Clean room',
    completed: false,
}

答案:

scala 复制代码
type myPick<T, K extends keyof T> = {
  [key in K]: T[key];
};

答案解析:

  1. myPick 接收两个参数:
  • T:是一个任意类型
  • K extends keyof T:表示 K 是 T 的所有属性名的子集
  1. [key in K]: T[key];:如果 key 在 K 中,便筛选出 T[key]

二 对象属性只读

题目描述

不要使用内置的 Readonly,自己实现一个。泛型 Readonly 会接收一个 泛型参数,并返回一个完全一样的类型,只是所有属性都会是只读 (readonly) 的。也就是不可以再对该对象的属性赋值。 例如:

vbnet 复制代码
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

答案

ini 复制代码
type myReadonly<T> = {
  readonly [key in keyof T]: T[key];
};

答案解析:

  1. myReadonly: 接收一个泛型参数。
  2. readonly [key in keyof T]: T[key];:给所有属性加上readonly

三 元组转换为对象

题目描述:

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

ini 复制代码
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'}

答案

scala 复制代码
type TupleToObject<T extends readonly (keyof any)[]> = {
  [key in T[number]]: key;
};

答案解析

  1. T extends readonly (keyof any)[]: 表示 T 是一个元组或数组类型,其元素是任何可能的属性名(即,string 或 number 或 symbol)。

  2. [key in T[number]]: key; 是一个映射类型,它遍历 T 中的每一项,将每一个元素值 key 映射为键,并且值也是元素值 key。

四 第一个元素

题目描述

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

例如:

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

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

答案

scala 复制代码
type First<T extends (keyof any)[]> = T[0];

答案解析

  1. T extends (keyof any)[]:T 接收一个元素为任意类型的数组
  2. 返回 T[0]

五 获取元组长度

题目描述

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

例如:

ini 复制代码
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

答案

scala 复制代码
type Length<T extends Readonly<Array<any>>> = T["length"];

答案解析:

  1. T extends Readonly<Array<any>>:T 为只读类型的数组
  2. T["length"]:返回 T 的长度

六 实现 Exclude

题目描述

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

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

例如:

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

答案

r 复制代码
type MyExclude<T, U> = T extends U ? never : T;

答案解析

  1. T 和 U 是任意类型。
  2. T extends U ? never : 如果 T 可以赋值给 U,则结果类型是 never,否则结果类型是 T。

七 Awaited

题目描述

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

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

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

type Result = MyAwaited<ExampleType> // string

答案

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

答案解析

  1. T extends PromiseLike<any>: 表示 T 是一个 Promise类 Promise 类型。
  2. T extends PromiseLike<infer U> ? U : never: 是一个条件类型,如果 T 是一个 Promise 类型,则结果类型是 Promise 的返回值类型 U,否则结果类型是 never。
  3. U extends PromiseLike<any> ? MyAwaited<U> : U 是一个条件类型,如果 U 仍然是一个 Promise 类型,则递归调用 MyAwaited 类型来提取返回值类型,否则结果类型就是 U。

八 If

题目描述

实现一个 IF 类型,它接收一个条件类型 C ,一个判断为真时的返回类型 T ,以及一个判断为假时的返回类型 F。 C 只能是 true 或者 false, T 和 F 可以是任意类型

例如:

ini 复制代码
type A = If<true, 'a', 'b'> // expected to be 'a'
type B = If<false, 'a', 'b'> // expected to be 'b'

答案

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

答案解析

  1. C extends Boolean :表示 C 是一个布尔类型,可以是 true 或 false。
  2. TF :是任意类型。
  3. C extends true ? T : F: 是一个条件类型,如果 C 是 true,则结果类型是 T,否则结果类型是 F。

九 Concat

题目描述

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

例如:

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

答案

scala 复制代码
type Concat<T extends any[], U extends any[]> = [...T, ...U];

答案解析

  1. T extends any[], U extends any[]:T,U 为任意类型的数组
  2. [...T, ...U]:将 T,U 结构后放入同一个数组并返回

十 Includes

题目描述

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

例如:

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

答案:

scala 复制代码
type Includes<T extends readonly any[], U> = T extends [
  infer First,
  ...infer Last
]
  ? myEqual<U, First> extends true
    ? true
    : Includes<Last, U>
  : false;

答案解析

  1. T extends readonly any[] 表示 T 是一个只读的元组或数组类型。
  2. U 是任意类型。
  3. T extends [infer First, ...infer Last]: 是一个条件类型和类型推断的组合,如果 T 是一个非空的元组或数组类型,则 First 是 T 的第一个元素的类型,Last 是 T 的剩余元素的类型组成的元组。
  4. myEqual<U, First> extends true ? true : Includes<Last, U> :是一个条件类型,如果 U 和 First 是相同的类型,则结果类型是 true,否则递归检查 U 是否在 Last 中

十一 Push

题目描述

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

例如:

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

答案

scala 复制代码
type Push<T extends any[], U> = [...T, U];

答案解析

  1. T extends any[]:T 为任意类型的数组
  2. [...T, U]:将 T 展开与 U 放在同一个数组

十二 unshift。

题目描述

实现类型版本的 Array.unshift。

ini 复制代码
type Result5 = Unshift<[1, 2], 0>; // [0, 1, 2,]

答案

scala 复制代码
type Unshift<T extends any[], U> = [U, ...T];

答案解析

  1. T extends any[]:T 为任意类型的数组
  2. [U, ...T]:将 T 展开与 U 放在同一个数组

十三 Parameters

题目描述

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

例如:

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

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

答案

scala 复制代码
type MyParameters<T extends (...agrs: any) => any> = T extends (
  ...args: infer S
) => any
  ? S
  : any;

答案解析

  1. T extends (...args: any) => any: 表示 T 是一个函数类型
  2. T extends (...args: infer S) => any ? S : any: 是一个条件类型和类型推断的组合,如果 T 是一个函数类型,则结果类型是函数的参数类型列表 S,否则结果类型是 any

十四 shift

题目描述

实现类型版本的 Array.shift。

答案

scala 复制代码
type shift<T extends any[]> = T extends [infer First, ...infer Rest]
  ? Rest
  : never;

答案解析

  1. T extends any[] 表示 T 是一个元组数组类型
  2. T extends [infer First, ...infer Rest] ? Rest : never :是一个条件类型和类型推断的组合,如果 T 是一个非空的元组或数组类型,则 First 是 T 的第一个元素的类型,Rest 是 T 的剩余元素的类型组成的元组。结果类型是 Rest,否则结果类型是 never

十五 pop

题目描述

实现类型版本的 Array.pop。

答案

scala 复制代码
type shift<T extends any[]> = T extends [...infer Rest, infer last]
  ? Rest
  : never;

答案解析

  1. T extends any[] 表示 T 是一个元组数组类型
  2. T extends [...infer Rest, infer last] ? Rest : never :是一个条件类型和类型推断的组合,如果 T 是一个非空的元组或数组类型,则 Rest 是 T last 是 T 的最后一个元素。结果类型是 Rest,否则结果类型是 never

总结

通过学习和实践15个简单的TypeScript工具函数,我深信我们对TypeScript已经有了更深入的理解和认识。然而,学习的旅程永无止境,接下来我将进一步探索和学习中等难度的TypeScript工具函数。

这是一个共同提升,共同进步的旅程。让我们继续一道前行,一起成长吧!

相关推荐
吕彬-前端9 分钟前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱12 分钟前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
guai_guai_guai21 分钟前
uniapp
前端·javascript·vue.js·uni-app
bysking1 小时前
【前端-组件】定义行分组的表格表单实现-bysking
前端·react.js
王哲晓2 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
fg_4112 小时前
无网络安装ionic和运行
前端·npm
理想不理想v2 小时前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试
酷酷的阿云2 小时前
不用ECharts!从0到1徒手撸一个Vue3柱状图
前端·javascript·vue.js
微信:137971205872 小时前
web端手机录音
前端
齐 飞2 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb