iT邦幫忙

2024 iThome 鐵人賽

DAY 17
0
JavaScript

TypeScript Type Challenges 冒險篇章:30 天闖關之旅,type 簡單了?你確定?系列 第 17

第17關:Deep Readonly!TypeScript 水不在深,有龍則靈: Deep Readonly

  • 分享至 

  • xImage
  •  

第17關:Deep Readonly

關卡簡介

Implement a generic DeepReadonly<T> which make every parameter of an object - and its sub-objects recursively - readonly.

You can assume that we are only dealing with Objects in this challenge. Arrays, Functions, Classes and so on do not need to be taken into consideration. However, you can still challenge yourself by covering as many different cases as possible.

實現一個泛型 DeepReadonly<T>,將物件的每個屬性以及其子物件遞歸地設為只讀。

在這個挑戰中,你可以假設我們只處理物件類型。不需要考慮陣列、函數、類別等其他類型。然而,你仍可以挑戰自己,嘗試覆蓋更多不同的情況。

任務說明:

type X = { 
  x: { 
    a: 1
    b: 'hi'
  }
  y: 'hey'
}

type Expected = { 
  readonly x: { 
    readonly a: 1
    readonly b: 'hi'
  }
  readonly y: 'hey' 
}

type Todo = DeepReadonly<X> // should be same as `Expected`

接下來,你的任務是讓下面的type cases測試通過:

type cases = [
  Expect<Equal<DeepReadonly<X1>, Expected1>>,
  Expect<Equal<DeepReadonly<X2>, Expected2>>,
]

type X1 = {
  a: () => 22
  b: string
  c: {
    d: boolean
    e: {
      g: {
        h: {
          i: true
          j: 'string'
        }
        k: 'hello'
      }
      l: [
        'hi',
        {
          m: ['hey']
        },
      ]
    }
  }
}

type Expected1 = {
  readonly a: () => 22
  readonly b: string
  readonly c: {
    readonly d: boolean
    readonly e: {
      readonly g: {
        readonly h: {
          readonly i: true
          readonly j: 'string'
        }
        readonly k: 'hello'
      }
      readonly l: readonly [
        'hi',
        {
          readonly m: readonly ['hey']
        },
      ]
    }
  }
}

type X2 = { a: string } | { b: number }

type Expected2 = { readonly a: string } | { readonly b: number }

冒險指南:

從以下幾個方向來思考:

  • 遞歸處理 (Recursion): 由於要求對物件的屬性和其子物件遞歸地應用 readonly,你需要使用遞歸型別。這意味著每當屬性是物件時,就要繼續對其內部屬性應用相同的處理。

  • 映射型別 (Mapped Types): 映射型別允許你遍歷物件的屬性並進行修改。在這裡,我們需要為每個屬性設置 readonly 並對可能是物件的屬性進行遞歸處理。

  • 條件型別 (Conditional Types): 使用條件型別判斷每個屬性是否為物件,然後遞歸應用 DeepReadonly。如果屬性不是物件,就僅應用 readonly

通關方式:

解法1:

type DeepReadonly<T> = {
  readonly [key in keyof T]: keyof T[key] extends never
  ? T[key]
    : DeepReadonly<T[key]>;
};

細節分析:

  • keyof T[key] 判斷:
    我們使用 keyof T[key] 來檢查 T[key] 是什麼類型。如果 keyof T[key] 結果為 never,表示 T[key] 沒有任何鍵,這意味著它是一個基本類型(如數字、字串、布林值等)或者其他非物件類型(例如函數)。這種情況下,我們不需要進行遞歸處理,直接返回該屬性本身,保留其原始的值。

  • 遞歸處理物件:
    如果 T[key] 是一個物件(即它有鍵),我們就需要對它應用遞歸處理。通過 DeepReadonly<T[key]>,該屬性的所有子屬性也將被設置為 readonly。這樣做可以確保深層嵌套的物件也同樣是不可變的。這種遞歸方式是處理嵌套物件的核心邏輯。

  • readonly 應用:
    映射型別 readonly [key in keyof T] 用於遍歷 T 的所有屬性,並將每個屬性設置為 readonly。這樣,所有第一層的屬性無論是否為物件,都會被設置為只讀。當屬性為物件時,還會進行遞歸,進一步將其子屬性也設置為只讀。

這樣,我們就能順利通過測試啦 🎉 😭 🎉

總結:

本次介紹了 Deep Readonly 的實作,下一關會挑戰 Tuple to Union,期待再相見!


上一篇
第16關:Readonly 2!TypeScript 可遠觀不可褻玩焉: Readonly 保護罩 2
下一篇
第18關:Tuple to Union !TypeScript 易容術 :元組變聯合型別
系列文
TypeScript Type Challenges 冒險篇章:30 天闖關之旅,type 簡單了?你確定?30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言