挑战
实现一个泛型MyReadonly2<T, K>,它带有两种类型的参数T和K。
类型 K 指定 T 中要被设置为只读 (readonly) 的属性。如果未提供K,则应使所有属性都变为只读,就像普通的Readonly<T>一样。
例如
interface Todo {
title: string;
description: string;
completed: boolean;
}
const todo: MyReadonly2<Todo, 'title' | 'description'> = {
title: 'Hey',
description: 'foobar',
completed: false,
};
todo.title = 'Hello'; // Error: cannot reassign a readonly property
todo.description = 'barFoo'; // Error: cannot reassign a readonly property
todo.completed = true; // OK解答
这道题在 Readonly<T> 的基础上添加了一个新的类型参数 K,可以让我们将指定的属性设置为只读。我们可以基于 Readonly<T> 来修改。
type MyReadonly2<T, K> = {
readonly [P in keyof T]: T[P];
};因为只有 K 中的属性会被设置成 readonly,所以我们把 T 中的属性分成两部分,K 和 另外的部分,另外的部分可以通过 Omit<T, K> 得到。要将两部分合并成一个类型,可以使用交叉类型。因此我们将 MyReadonly2<T, K> 修改为这样:
type MyReadonly2<T, K> = {
readonly [P in K]: T[P];
} & Omit<T, K>;现在 TS 会报一个错误:Type 'P' cannot be used to index type 'T'. 因为我们还没有对 K 进行约束,K 应该是 T 中的键。
type MyReadonly2<T, K extends keyof T> = {
readonly [P in K]: T[P];
} & Omit<T, K>;现在还有一个 test case 报错,因为题目提到了如果未提供 K,则应使所有属性都变为只读。所以我们需要给 K 指定默认值。
所以最终实现为:
type MyReadonly2<T, K extends keyof T = keyof T> = {
readonly [P in K]: T[P];
} & Omit<T, K>;