iT邦幫忙

2024 iThome 鐵人賽

DAY 16
0
JavaScript

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

第16關:Readonly 2!TypeScript 可遠觀不可褻玩焉: Readonly 保護罩 2

  • 分享至 

  • xImage
  •  

第16關:Readonly 2

關卡簡介

Implement a generic MyReadonly2<T, K> which takes two type argument T and K.

K specify the set of properties of T that should set to Readonly. When K is not provided, it should make all properties readonly just like the normal Readonly<T>.

實現一個泛型 MyReadonly2<T, K>,它接受兩個型別參數 TK

K 用來指定 T 中應設為 Readonly 的屬性集。如果沒有提供 K,則應將所有屬性設為 readonly,就像普通的 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

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

type cases = [
  Expect<Alike<MyReadonly3<Todo1>, Readonly<Todo1>>>,
  Expect<Alike<MyReadonly3<Todo1, 'title' | 'description'>, Expected>>,
  Expect<Alike<MyReadonly3<Todo2, 'title' | 'description'>, Expected>>,
  Expect<Alike<MyReadonly3<Todo2, 'description' >, Expected>>,
]

interface Todo1 {
  title: string
  description?: string
  completed: boolean
}

interface Todo2 {
  readonly title: string
  description?: string
  completed: boolean
}

interface Expected {
  readonly title: string
  readonly description?: string
  completed: boolean
}

(Alike)

export type MergeInsertions<T> =
  T extends object
    ? { [K in keyof T]: MergeInsertions<T[K]> }
    : T

export type Alike<X, Y> = Equal<MergeInsertions<X>, MergeInsertions<Y>>

冒險指南:

從以下幾個方向來思考:

  • 屬性篩選與處理 (Property Selection & Handling): 需要從類型 T 中挑選出被指定的屬性 K,將其設為 readonly,而其他屬性保持原樣。你可以利用 keyof 來提取 T 的所有屬性,並使用條件型別來篩選屬性。

  • 泛型參數設計 (Generic Parameters): K 是可選的,當 K 未提供時,應將所有屬性設為 readonly,這類行為可以通過給 K 設置預設值來實現。

  • 映射型別 (Mapped Types): 映射型別將幫助我們重新構造 T,並對不同的屬性應用不同的處理邏輯。需要結合條件型別,對被篩選的屬性應用 readonly,而對其餘屬性保持不變。

  • 條件型別 (Conditional Types): 使用條件型別來判斷每個屬性是否屬於 K,從而決定是將其設為 readonly 還是保持原狀。

通關方式:

解法1:

type MyReadonly2<T, K extends keyof T = keyof T> = {  
  // 先利用never移除不要的:每一個T的P,是否有extends K(要選取的)?
  [P in keyof T as P extends K ? never : P]: T[P] 
} &
//  再將所選的加上readonly
//  readonly [P in keyof T as P extends K ? P : never]: T[P];
//  針對上面這行,我們其實只需要`P extends K ? P`所以可以解化成...`[P in K]`,each key P in the union of keys K
{ readonly [P in K]: T[P] }

解法2:

// type MyReadonly2<T, K extends keyof T = keyof T> = Omit<T,K> & Readonly<Pick<T,K>>

細節分析:

  • 有選的要readonly, 沒有的維持原樣, 完全沒給全部readonly
  1. Pick 挑選 "要" readonly的,刪除(狀態維持不動的properties)
  2. Omit 刪除 "被" readonly的,留下(狀態維持不動的properties)

以上達成互補

  1. 沒給時... When K is not provided, it should make all properties readonly just like the normal Readonly<T>
    也就是說沒有提供K時,當作K是全部的keyof T => set a default value for K that includes all keys of T

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

總結:

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


上一篇
第15關:Omit!TypeScript Expelliarmus:去去,屬性走
下一篇
第17關:Deep Readonly!TypeScript 水不在深,有龍則靈: Deep Readonly
系列文
TypeScript Type Challenges 冒險篇章:30 天闖關之旅,type 簡單了?你確定?30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言