title | date | hideComments |
---|---|---|
3:Readonly2 |
2020/05/29 |
false |
实现一个通用 MyReadonly2<T, K>
,它带有两种类型的参数 T
和 K
。
K
指定应设置为 Readonly
的 T
的属性集。如果未提供 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
::: info 提示 在这个挑战中,你需要修改下方的代码使得测试通过(使其没有类型错误)。并且记得将答案分享奥。 :::
这个挑战是 Readonly<T>
挑战的延续,除了需要添加一个新的类型参数 K
,以便我们可以 将指定的属性设为只读外,一切都基本相同。
我们从最简单的例子开始,即 K 是一个空集合,因此没有任何属性需要设置为只读。我们 只需要返回 T 就好了。
type MyReadonly2<T, K> = T;
现在我们需要处理这样一种情况:即在 K 中提供对应属性,我们利用&操作符使两种类型 产 生交集: 一个是之前提到的类型 T,另一个是含有只读属性的类型。
type MyReadonly2<T, K> = T & { readonly [P in K]: T[P] };
看起来是一种解决方案,但是我们得到一个编译错误 :Type P cannot be used to index type T
。这是对的,因为我们没有对 K 设置约 束,它应该是 “T 中的每一个键”
:
type MyReadonly2<T, K extends keyof T> = T & { readonly [P in K]: T[P] };
正常工作啦? 🙅 不!
我们还没有处理当 K 什么都没有设置的情况,该情况下我们的类型必须和通常 的 Readonly<T>
表现得一样。为了修复这个问题,我们将 K 的默认值设为”T 的所有键 “。
// solution-1
type MyReadonly2<T, K extends keyof T = keyof T> = T & {
readonly [P in K]: T[P];
};
// 即:
type MyReadonly2<T, K extends keyof T = keyof T> = Omit<T, K> & Readonly<T>;
你可能发现 solution-1 在 TypeScript 4.5 及以上的版本中不能正常工作,因为原本的行 为在 TypeScript
中是一个 bug( 在 microsoft/TypeScript#45122 中 列出, 在 microsoft/TypeScript#45263
中 被修复,在 TypeScript 4.5 版本中正式发布)。从概念上来说,交叉类型意味着 “与”, 因此{readonly a: string} & {a: string}与{a: string}应该是相等的,也就是说属 性 a 是可读且可写的。
在 TypeScript 4.5 之前, TypeScript 有着相反的不正确的行为,也就是说在交叉类型中 ,一些成员的属性是只读的,但在另外成员中同名属性是可读可写的,最终对象的相应属性 却是只读的,这种行为是不正确的,但这已经被修复了。因此这也就解释了为什 么 solution-1 不能正常工作。想要解决这个问题,可以像下面这样写:
//Solution-2
type MyReadonly2<T, K extends keyof T = keyof T> = Omit<T, K> & {
readonly [P in K]: T[P];
};
//i.e.
type MyReadonly2<T, K extends keyof T = keyof T> = Omit<T, K> & Readonly<T>;
因为 K 中的键都没有在 keyof Omit<T, K>
中出现过,因此 solution-2
能够向相应属性 添加 readonly
修饰符。