ts类型挑战【二】

172 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第4天,点击查看活动详情

题目二:easy-readonly

// type-challenges\7-easy-readonly\template.ts
type MyReadonly<T> = any
// type-challenges\7-easy-readonly\test-cases.ts
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
  }
}

在这道题中,我们实现的 MyReadonly 需要和系统实现的 Readonly 效果一致。

效果是调用 MyReadonly<Todo1> 后得到的类型会将 Todo1 中的所有类型都加上 readonly 修饰符:

type Type3 = MyReadonly<Todo1>

等同于下面的效果:

type Type3 = {
    readonly title: string;
    readonly description: string;
    readonly completed: boolean;
    readonly meta: {
        author: string;
    };
}

我们为了实现上面的效果,需要做如下这些工作:

  • 返回一个对象

  • 遍历传入的对象(接口)

  • 加上 readonly 关键字

  • 通过 key 获取传入对象(接口)的值,接口的值就是类型

1. 返回一个对象

type MyReadonly<T> = {  }

2. 遍历接口

使用 in 来遍历接口;

使用 keyof 获取 T 中的全部 key

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

3. 获取对应的值

js 类似,通过 T[P] 就可以获取到传入接口的相应值(类型)了。类似于 obj[key]

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

4. 加上 readonly 关键字

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

题目三:easy-tuple-to-object

// template.ts
type TupleToObject<T extends readonly any[]> = any
// test-cases.ts
import { Equal, Expect } from '@type-challenges/utils'

const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const

type cases = [
  Expect<Equal<TupleToObject<typeof tuple>, { tesla: 'tesla'; 'model 3': 'model 3'; 'model X': 'model X'; 'model Y': 'model Y'}>>,
]

// @ts-expect-error
type error = TupleToObject<[[1, 2], {}]>

1. typeof

先来看测试代码:

const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const

这里声明的是 js 变量or常量(let,const 这类都属于 js 内容

而下面的 type cases ,却属于 ts 中的类型,变量和类型属于两个不同的轨道线;如果想使用变量对类型进行一定的操作的话,就需要使用 typeof 这个关键字了,也就是我们测试代码中 TupleToObject 传入的参数 typeof tuple

我们可以看一下,使用 typeof 关键字转换 tuple 后,它是什么内容:

type r = typeof tuple

把鼠标放在 r 上,可以看到 r 的值为:type r = readonly ["tesla", "model 3", "model X", "model Y"]

2. as const

如果我们不写 as const 的话,来看看效果

const tuple = ['tesla', 'model 3', 'model X', 'model Y']
type r = typeof tuple

此时,把鼠标放在 r 上,可以看到 type r = string[]。发现它是一个包含 string 的数组类型。

这里就会涉及到一个比较基础的知识点:字面量类型

通过 let 创建

let str = "abc"
type t = typeof str // type t = string

如果我们是通过 let 创建的 str,则 type t = string

通过 const 创建

const str = "abc"
type t = typeof str // type t = "abc"

如果我们是通过 const 创建的 str,则 type t = "abc"。意味着类型 t 是不可以被修改的。

原因

const 创建的是一个常量,我们只要创建好了这个常量,它就不可被修改了。所以把它应用到类型之后,它就变成了字面量类型

回到我们的 as const,如果我们使用了 as const,相当于将数组 tuple 中的元素都变成了常量,之后不能再对齐修改了。

例如:tuple[0] = "abc",就会报错。

3. 题解

  1. 返回一个对象
  2. 遍历数组(类型数组)

我们先尝试一下使用 keyof 来遍历数组

type TupleToObject<T extends readonly any[]> = {
  [P in keyof T]: P 
}

来看一下测试:

const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const
type r = TupleToObject<typeof tuple> // type r = readonly ["0", "1", "2", "3"]

可以看到,r 虽然不是我们想要的内容,但是我们误打误撞拿到了数组的下标索引。

那么,应该怎样去遍历数组呢?

这里涉及到一个新的语法 [number],让我们来试一下

type TupleToObject<T extends readonly any[]> = {
  [P in T[number]]: P 
}

再来看一下测试代码

const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const
type r = TupleToObject<typeof tuple> // type r = { tesla: "tesla"; "model 3": "model 3"; ...}

搞定~

4. 遍历数组:[number]

ts 中遍历数组的方式 [number]

5. 期望报错

// @ts-expect-error
type error = TupleToObject<[[1, 2], {}]>

在测试代码中,这里是期望会报错的

TupleToObject 中,它是不可以接收数组对象类型的。

对象的 key 只能接收三种类型 number/string/symbol,所以我们只需要在 TupleToObject 的接收入参的时候进行限制就可以了

type TupleToObject<T extends readonly (string|number|symbol)[]> = {
  [P in T[number]]: P 
}