首先我要讲解一下 mapped (映射类型)
使用 Object.freeze()方法。把对象被冻结后,就不可能在添加、更改或删除属性。
我们看看在不使用映射类型的代码:
// 定义一个包含两个属性 x 和 y 为number 的 Point 接口。
type Point = {
x: number;
y: number;
}
// 定义另一个接口 readonlyPoint,它的所有属性都使用 readonly 关键字
type ReadonlyPoint = {
readonly x: number;
readonly y: number;
}
// FreezePoint 函数接受一个 Point 作为参数,冻结它,
// 并将冻结的对象返回给调用者, 因此其内属性被转化为只读。
function freezePoint(p: Point): ReadonlyPoint {
return Object.freeze(p);
}
const location = freezePoint({ x: 0, y: 0 });
location.x = 100;
//无法分配到 "x" ,因为它是只读属性。ts(2540)
看到上面一大坨代码,你可能会觉得很麻烦, 现在我们试试系统提供给我们的 Readonly
function freezePoint<T>(p: T): Readonly<T> {
return p
}
此时你会发现代码居然就只需要这几行代码,太神奇了吧
那么它的实现原理是什么呢
type MyReadonly<T> = {
readonly [P in keyof T]: T[P];
};
如果第一次接触这个代码会觉得非常难理解,下面我来解读一下
1 我定义了一个通用的 Readonly 类型,它有一个名为 T 的类型参数。
2 在方括号内,我们使用 keyof 运算符。 keyof: T 将所有属性名称 转换为 联合类型。
type Todo = {
title: string;
description: string;
completed: boolean;
}
type TodoKeys = keyof Todo;
function foo(k:TodoKeys) {}
foo("")
转换后为 title | description | completed
3 方括号中的 in 关键字 表示我们正在处理映射类型。
或者说是在 联合类型中 遍历每一个成员
[P in keyof T]:T[P]表示T类型的每个属性P的类型都转换为T[P]。
4 T[P] 类型是根据 索引/字符串 查找类型。它表示 T 类型 的属性 P 的类型。
5 最后,readonly 修饰符指定每个属性都应转换为只读属性。
现在我假设上面的代码和文字你是没理解明白的,所以我还提供了另一种案例
// 原始的状态
type Point = {
x: number;
y: number;
}
type ReadonlyPoint = {
readonly [P in keyof Point]: Point[P];
};
// 去除 keyof ,所表示的代码
type ReadonlyPoint = {
readonly [P in "x" | "y"]: Point[P];
};
// 去除左边方括号的代码,并且模拟遍历后的代码
type ReadonlyPoint = {
readonly x: Point["x"];
readonly y: Point["y"];
};
// 遍历结束后的代码
type ReadonlyPoint = {
readonly x: number;
readonly y: number;
};
下面给出官网的资料 mapped (映射类型)
现在就可以开开心心的做题目啦
不要使用内置的Readonly<T>,自己实现一个。
该 Readonly 会接收一个 泛型参数,并返回一个完全一样的类型,只是所有属性都会被 readonly 所修饰。
也就是不可以再对该对象的属性赋值。
题目Readonly
type MyReadonly<T> = any
type cases = [
Expect<Equal<MyReadonly<Todo1>, Readonly<Todo1>>>,
]
interface Todo1 {
title: string
description: string
completed: boolean
meta: {
author: string
}
}
type Todo2 = {
title: string
description: string
completed: boolean
meta: {
author: string
}
}