# 前置知识
今日 【middle - 8.readonly2】
在这之前需要掌握一点Ts基础知识,可以参考学习记录TypeScript学习记录-[数据类型]、TypeScript学习记录-[类和接口]、TypeScript学习记录-[枚举和泛型]、TypeScript学习记录-[类型别名]
# 二、题目分析
# readonly2
实现一个通用MyReadonly2<T, K>,它带有两种类型的参数T和K。K指定应设置为Readonly的T的属性集。如果未提供K,则应使所有属性都变为只读,就像普通的Readonly
type MyReadonly2<T, K> = any
/* _____________ 测试用例 _____________ */
import type { Alike, Expect } from '@type-challenges/utils'
type cases = [
Expect<Alike<MyReadonly2<Todo1>, Readonly<Todo1>>>,
Expect<Alike<MyReadonly2<Todo1, 'title' | 'description'>, Expected>>,
Expect<Alike<MyReadonly2<Todo2, 'title' | 'description'>, Expected>>,
]
interface Todo1 {
title: string
description?: string
completed: boolean
}
interface Todo2 {
readonly title: string
description?: string
completed: boolean
}
interface Expected {
readonly title: string
readonly description?: string
completed: boolean
}
- 首先可以确定的是返回类型是
object(索引类型index Type), 且其中属于泛型K的属性都是readonly属性,即type MyReadonly2<T, keys extends keyof T> = { readonly [key in keys]: T[key] },其中keys extends keyof T对联合类型keys进行类型约束,确保后续使用in遍历keys时的每一个类型key均在泛型T的类型中 - 接下来要取包含在泛型
T但不包含在泛型k(keys)中的属性,可以这样写[key in keyof T as key extends keys ? never : key]: T[key],这里的重映射运算符as将在遍历运算中的每一项类型key使用extends ? :进行条件判断,符合前述条件的类型将被返回,而属于泛型k(keys)的属性将被丢弃(never代表不可达) - 经过上述分析我们将得到
type MyReadonly2<T, keys extends keyof T> = { readonly [key in keys]: T[key] } & { [key in keyof T as key extends keys ? never : key]: T[key] },这里通过交叉类型(Intersection)运算对类型做合并(两者都属于obj类型,同一类型可以合并,不同的类型无法合并),这样经过MyReadonly2运算就得到了一个K指定应设置为Readonly的T的属性集 - 值得注意的是
<T, Keys extends keyof T> = { [key in keyof T as key extends Keys ? never : key]: T[key] }是Omit<T, K>泛型的实现,前述内容可以简化为type MyReadonly2<T, keys extends keyof T> = { readonly [key in keys]: T[key] } & Omit<T, K>
- 为了实现
如果未提供K,则应使所有属性都变为只读,就像普通的Readonly<T>一样这个条件,在keys extends keyof T中,需要对泛型keys设定原始类型,即keys = keyof T,加上类型约束就得到了keys extends keyof T = keyof T,从而得到了最终的结果type MyReadonly2<T, keys extends keyof T = keyof T> = { readonly [key in keys]: T[key] } & { [key in keyof T as key extends keys ? never : key]: T[key] }