一起养成写作习惯!这是我参与「掘金日新计划 · 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. 题解
- 返回一个对象
- 遍历数组(类型数组)
我们先尝试一下使用 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
}