iT邦幫忙

2024 iThome 鐵人賽

DAY 11
0
JavaScript

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

第11關:Includes!TypeScript 包青天:元素不見立刻判決,無情 false

  • 分享至 

  • xImage
  •  

第11關:Includes

關卡簡介

Implement the JavaScript Array.includes function in the type system. A type takes the two arguments. The output should be a boolean true or false.

在型別系統中實現 JavaScriptArray.includes 函數。該型別接受兩個參數,輸出應為 truefalse 的布林值。

任務說明:

type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`

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

type cases = [
  Expect<Equal<Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Kars'>, true>>,
  Expect<Equal<Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'>, false>>,
  Expect<Equal<Includes<[1, 2, 3, 5, 6, 7], 7>, true>>,
  Expect<Equal<Includes<[1, 2, 3, 5, 6, 7], 4>, false>>,
  Expect<Equal<Includes<[1, 2, 3], 2>, true>>,
  Expect<Equal<Includes<[1, 2, 3], 1>, true>>,
  Expect<Equal<Includes<[{}], { a: 'A' }>, false>>,
  Expect<Equal<Includes<[boolean, 2, 3, 5, 6, 7], false>, false>>,
  Expect<Equal<Includes<[true, 2, 3, 5, 6, 7], boolean>, false>>,
  Expect<Equal<Includes<[false, 2, 3, 5, 6, 7], false>, true>>,
  Expect<Equal<Includes<[{ a: 'A' }], { readonly a: 'A' }>, false>>,
  Expect<Equal<Includes<[{ readonly a: 'A' }], { a: 'A' }>, false>>,
  Expect<Equal<Includes<[1], 1 | 2>, false>>,
  Expect<Equal<Includes<[1 | 2], 1>, false>>,
  Expect<Equal<Includes<[null], undefined>, false>>,
  Expect<Equal<Includes<[undefined], null>, false>>,
]

冒險指南:

從以下幾個方向來思考:

  1. 逐一比對:我們需要將陣列 (Array) 中的每個元素 (elements) 依次與目標 U 做比較,這可以透過遞迴 (recursion) 的方式來實現。
  2. 當發現 elements 匹配時,我們可以立即返回 true,否則繼續遞迴直到陣列耗盡,最後返回 false
  3. 如何處理空陣列 (Empty Array):若 array 已經空了,那麼就應該直接返回 false,這是遞迴的停止條件。

通關方式:

解法:

  type Includes<T extends readonly any[], U> = T extends [infer First, ...infer Rest] ? 
  (Equal<First, U> extends true ? true :  Includes<Rest, U> ): 
  false

(補充這裡使用的 equal)

export type Equal<X, Y> =
  (<T>() => T extends X ? 1 : 2) extends
  (<T>() => T extends Y ? 1 : 2) ? true : false

細節分析:

  • 泛型參數 (Generic Parameters):
    • T extends readonly any[]:這表示 T 是一個只讀的陣列 (readonly array)。extends readonly any[] 用來限制 T 必須是陣列。
    • U 是我們需要比對的目標值 (target value),型別可以是任何型別 (any type)。
  • 條件型別與推斷 (Conditional Types and Inference):
    • 使用 T extends [infer First, ...infer Rest] 來將陣列的第一個元素 (first element) 和剩下的部分 (remaining elements) 分別命名為 FirstRest。如果 T 是空陣列 (empty array),這個條件不成立,直接返回 false
    • 接著使用 Equal<First, U> 比較當前元素 First 與目標 U 是否相等。如果相等,則返回 true,結束遞迴 (end recursion)。否則,繼續遞迴處理剩餘的陣列 Rest
  • 遞迴與停止條件 (Recursion and Stopping Conditions):
    • 遞迴過程中 (during the recursion),不斷地將剩餘的元素傳入 (pass the remaining elements in),直到比對成功 (match found) 或者陣列耗盡 (array is exhausted),即 T 為空,返回 false

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

其他補充:

空陣列、單元素陣列與多元素陣列的推斷 (Infer) 結果:

type IsEmpty = [] extends any[] ? true : false; // true
type FirstEmpty = [] extends [infer F, ...infer R] ? F : false; // false
type FirstSingle = [1] extends [infer F, ...infer R] ? F : false; // 1
type RestSingle = [1] extends [infer F, ...infer R] ? R : false; // []
type FirstMultiple = [1, 'hi'] extends [infer F, ...infer R] ? F : false; // 1
type RestMultiple = [1, 'hi'] extends [infer F, ...infer R] ? R : false; // ['hi']
  • 進階題1:IncludesAny
// 如果我們的條件改為,想要 T 存在 任意 U 裡面的元素,該如何實作?
type IncludesAny<T extends any[], U extends any[]> =  any

// 並讓以下通過呢?:
type cases0 = [
 Expect<Equal<IncludesAny<['Kars', 'Esidisi', 'Wamuu', 'Bruno Mars'],  ['Black Pink!']>, false>>,
 Expect<Equal<IncludesAny<['Kars', 'Esidisi', 'Wamuu', 'Bruno Mars'], ['Black Pink!', 'Bruno Mars']>, true>>
 ]

解法:

type IncludesAny<T extends readonly any[], U extends readonly any[]> = 
U extends [infer First, ...infer Rest] ? 
(Includes<T, First> extends true ? true :  IncludesArrayAny<T, Rest> ): 
  false
  • 進階題2:IncludesAll
// 如果我們的條件改為,想要 T 存在 所有 U 裡面的元素,該如何實作?
type IncludesAll<T extends readonly any[], U extends readonly any[]> = any

// 並讓以下通過呢?
type cases1 = [
 Expect<Equal<IncludesAll<['Kars', 'Esidisi', 'Wamuu', 'Bruno Mars'], ['Wamuu', 'Bruno Mars']>, true>>,
 Expect<Equal<IncludesAll<['Kars', 'Esidisi', 'Wamuu', 'Bruno Mars'],  ['Black Pink', 'Bruno Mars']>, false>>,
 Expect<Equal<IncludesAll<['Kars', 'Esidisi', 'Wamuu', 'Bruno Mars'], []>, false>>
]

解法:

type IncludesAll<T extends readonly any[], U extends readonly any[]> =
  U extends [infer First, ...infer Rest]
 ? (Includes<T, First> extends true ? (Rest extends [] ? true : IncludesArrayAll<T, Rest>) : false) 
  : false;

關鍵字補給:

  • recursion : 未來補充 😭
  • infer : 未來補充 😭

總結:

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


上一篇
第10關:Concat!TypeScript 陣列拼圖:合併出完美結局
下一篇
第12關:Push + Unshift!TypeScript 貪食蛇:如何靈活控制陣列頭尾
系列文
TypeScript Type Challenges 冒險篇章:30 天闖關之旅,type 簡單了?你確定?30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言