类型体操之实现 type-challenges 中的 built-in 的所有类型

简介: #built-in 是 type-challenges 库中的一个 tag,里面一共包括了 7 个类型,其中前两个就是之前介绍过的 类型体操之实现 Pick 和 Omit 中的 Pick 和 Omit

#built-intype-challenges 库中的一个 tag,里面一共包括了 7 个类型,其中前两个就是之前介绍过的类型体操之实现 Pick 和 Omit 中的 PickOmit,这 7 个类型如下

  1. Pick
  2. Readonly
  3. Exclude
  4. Awaited
  5. Parameters
  6. ReturnType
  7. Omit

都是 TS 的内置类型(buit-in),大多数都是属于 type-challenges 简单难度的类型挑战

关于 type-challenges 库的介绍、其对应测试方法以及 PickOmit 类型的实现可以参考类型体操之实现 Pick 和 Omit

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

测试用例

/* _____________ 测试用例 _____________ */
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
  }
}

题目模板

/* _____________ 你的代码 _____________ */

type MyReadonly<T> = any

解答

Readonly 字如其名,就是只能读,ES6const 就是这种,const 的特性是浏览器提供的,并不是通过某种 JS 逻辑实现的,而 Readonlyts 也是如此

靠的是 tsreadonly 属性修饰符

IMG

readonly 可以在  class  interface  type  array-like 定义中使用它,也可以用来定义一个函数的参数。既然是只读的意味着一旦定义了就不能再修改,所以这些属性必须在声明的时候或者在类中对它进行初始化。
interface Point {
    readonly x: number;
    readonly y: number;
}
const start: Point = {
    x: 0,
    y: 0
}
start.x = 2 // 报错 Cannot assign to 'x' because it is a read-only property.

那么 readonly 的实现可以说是手到擒来了,解答如下

type MyReadonly<T> = {
  readonly [P in keyof T]: T[P]
}

用一个泛型 P 指代 T 中的任意一个属性,keyof T 则用于获取 T 类型的所有键,其返回类型是联合类型和 Object.keys() 非常像哦

const todo1 = {
  title: "",
  description: "",
  completed: true,
  meta: {
    author: "",
  }
}
Object.keys(todo1);
// (4)['title', 'description', 'completed', 'meta']

inJS 的原生运算符,如果指定属性在指定对象中,则 in 运算符返回 true,而对于 ts 来说,这种语法 [P in K] 可以用来遍历联合类型里的类型(注意,遍历每一个

T[P] 的意思就比较好理解了,interface 具象化为对象,T[P] 不就是表示 T 中属性为 P 的类型值吗?即 Todo['title']: string

留一个伏笔,注意上面的 Object.keys() 的返回结果,不包含 author 属性,而解答中的 Readonly 也一样,只是一个浅层的只读

Exclude

题目描述

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

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

例如:

type Result = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'

测试用例

/* _____________ 测试用例 _____________ */
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>>,
]

题目模板

/* _____________ 你的代码 _____________ */

type MyExclude<T, U> = any

解答

Exclude 不就是纯纯的联合类型版 Pick 吗?也是一个筛选,我直接一个 API 实现(JS 版)

const MyExclude = T.filter(item => {
  return !U.include(item);
})

而对于 TS 来说,就没有这么好用的函数、 lambda,其实现如下

type MyExclude<T, U> = T extends U ? never : T;

这个三元表达式可以视为 !U.include(item),也可以视为 T.filter(() => {...}),因为如果 T 是一个联合类型,则会对每一个 T 的成员执行一次这个三元表达式,即 T.item extends U ? never : T.item

never 就有意思了,never 通常用来表示一个从来不会有返回值的函数,而三元表达式实际上就是 lambda 函数,特殊一点的函数,因此在此处可以作为一个筛选的作用,即当 T extends U 为真时,不会有类型返回

Awaited

题目描述

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

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

type ExampleType = Promise<string>

type Result = MyAwaited<ExampleType> // string

测试用例

/* _____________ 测试用例 _____________ */
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 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>

题目模板

/* _____________ 你的代码 _____________ */

type MyAwaited<T> = any

解答

这道题不应该是简单

仔细观察题目描述,本身是一个很简单的例子

type Result = MyAwaited<Promise<string>> // string

实现上可以通过 extends 条件类型和 infer 推断类型,extendsExclude 中已经介绍过了,而 infer 其实就是一种 运行时 的类型,和泛型其实差不多,举个例子

type Awaited<T> = T extends Promise<infer U> ? U : T;

假设 T: Promise<string>U: string

type Awaited<Promise<string>> = Promise<string> extends Promise<string> ? string : Promise<string>;
type Awaited<Promise<string>> = string;

假设 T: Promise<string>U: number

type Awaited<Promise<string>> = Promise<string> extends Promise<number> ? string : Promise<string>;
type Awaited<Promise<string>> = Promise<string>;

但实际上在运行前定义时我们不知道 U 是啥,也没有传入,所以就需要使用 infer 来做个占位,留空,同时也避免和传入的泛型冲突

很有意思的特性

但实际上这样的写法过不了所有测试

type MyAwaited<T> = T extends Promise<infer U> ? U : T;

IMG

这就要求做题家需要了解一下 Promise 的两个特性,链式调用以及兼容其它 Promise 实现(可以谷歌一下)

链式调用其实就是嵌套调用,实现如下

type MyAwaited<T extends Promise<any>> = T extends Promise<infer R>
  ? R extends Promise<any>
    ? MyAwaited<R>
    : R
  : never;

如果 R 仍然属于 Promise<any> 类型,那么就再来一次 MyAwaited<R>,否则直接返回 R

兼容其它 Promise 实现,则可以使用 PromiseLike 这个内置类型

PS: 所谓的其它 Promise 实现就是必须具有 then 属性的对象

因此最终的实现如下

type MyAwaited<T extends PromiseLike<any>> = T extends PromiseLike<infer R>
  ? R extends PromiseLike<any>
    ? MyAwaited<R>
    : R
  : never;

Parameters

题目描述

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

例如:

const foo = (arg1: string, arg2: number): void => {}

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

测试用例

/* _____________ 测试用例 _____________ */
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>, []>>,
]

题目模板

/* _____________ 你的代码 _____________ */

type MyParameters<T extends (...args: any[]) => any> = any

解答

参考资料

  1. TypeScript 中的 readonly 类型 - Meowu
  2. What does the in keyword do in typescript?
  3. Never - 深入理解 TypeScript - 三毛
相关文章
|
6月前
|
机器学习/深度学习
GEE土地分类——Property ‘B1‘ of feature ‘LE07_066018_20220603‘ is missing.错误
GEE土地分类——Property ‘B1‘ of feature ‘LE07_066018_20220603‘ is missing.错误
79 0
|
6月前
Google Earth Engine(GEE)——ImageCollection.fromImages, argument ‘images‘: Invalid type. Expected type
Google Earth Engine(GEE)——ImageCollection.fromImages, argument ‘images‘: Invalid type. Expected type
54 0
|
6月前
|
前端开发
Google Earth Engine(GEE)——argument ‘input‘: Invalid type. Expected type: Image<unknown bands>错误
Google Earth Engine(GEE)——argument ‘input‘: Invalid type. Expected type: Image<unknown bands>错误
78 0
|
6月前
Google Earth Engine(GEE)——影像分类中出现的错误(Classifier confusionMatrix: Property ‘type‘ of feature ‘000000)
Google Earth Engine(GEE)——影像分类中出现的错误(Classifier confusionMatrix: Property ‘type‘ of feature ‘000000)
61 0
|
数据采集 运维 算法
Best Matching Unit,简称 BMU
最佳匹配单元(Best Matching Unit,简称 BMU)是自组织映射(Self-Organizing Maps,简称 SOM)算法中的一个重要概念。在 SOM 网络中,每个神经元都对应一个权重向量,表示该神经元对输入特征的响应。BMU 是指在 SOM 网络中与输入数据最相似的神经元,即具有与输入数据最接近的权重向量。在训练过程中
283 3
|
算法 数据可视化 机器人
Object SLAM: An Object SLAM Framework for Association, Mapping, and High-Level Tasks 论文解读
Object SLAM: An Object SLAM Framework for Association, Mapping, and High-Level Tasks 论文解读
84 0
|
测试技术
Ts类型体操Easy部分
Ts类型体操Easy部分
110 0
《Fighting Cybercrime A Joint Task Force of Real-Time Data and Human Analytics》电子版地址
Fighting Cybercrime: A Joint Task Force of Real-Time Data and Human Analytics
83 0
《Fighting Cybercrime A Joint Task Force of Real-Time Data and Human Analytics》电子版地址
如何解决 cdo转换经度-180~180 为0~360时出现报错:cdo sellonlatbox: Unsupported grid type: generic的问题
如何解决 cdo转换经度-180~180 为0~360时出现报错:cdo sellonlatbox: Unsupported grid type: generic的问题
如何解决 cdo转换经度-180~180 为0~360时出现报错:cdo sellonlatbox: Unsupported grid type: generic的问题
|
人工智能
P8969 幻梦 | Dream with Dynamic
P8969 幻梦 | Dream with Dynamic
172 0