TS 手写Readonly 学习类型体操( type-challenges ) 的第二天

292 阅读2分钟

首先我要讲解一下 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

Snipaste_2022-09-06_13-56-16.png

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
      }
    }