這個 TypeScript 技巧會讓你大吃一驚
從字符串數組中提取自定義類型
在 TypeScript 的世界里,自定義類型從字符串數組中顯現,就像隱藏的寶石。
TypeScript 是一個操縱現有數據和發展良好實踐的神奇工具。
今天,我們將探索如何以正確的方式從字符串數組中提取全名,以確保產生干凈的類型安全輸出。
那么,不多說了……讓我們直接開始吧。

問題
首先讓我們通過檢查這段代碼來理解其中的問題:
const names = ["Daniel Craciun", "John Doe", "Harry Pigeon"]
const findName = (surname: string) => {
return names.find((name) => name.includes(surname))
}
// 我們可以傳入任何字符串,這是不理想的。
console.log(findName("Craciun")) // 輸出:Daniel Craciun
console.log(findName("Doee")) // 輸出:undefined這段代碼使用一個名字數組來進行搜索。
函數 findName 接受一個字符串 surname 并返回關聯的全名。
問題出現在當你在 findName 函數中輸入 "Doee" 時。
這個不顯眼的拼寫錯誤導致輸出了 undefined,這可能會導致后續的錯誤,因為沒有任何東西阻止我們犯這種錯誤。
這就是 TypeScript 發揮作用的地方。
如果我們確保 findName 只接受字面上的姓氏,即 Craciun、Doe、Pigeon,那么當我們輸入像 "Doee" 這樣在名字數組中不存在的輸入時,編譯器應該會提出警告。
解決方案
我們已經確定了 findName 的有效參數只能是所有現有的姓氏。
為了實現這一點,我們創建了一個名為 ExtractSurname 的泛型類型。
ExtractSurname 的代碼可能看起來有點復雜,但我們將一步步拆解它:
type ExtractSurname<T extends string> = T extends `${infer Firstname} ${infer Surname}` ? Surname : null這里 ExtractSurname 接受一個泛型參數 T,它引用任何字面字符串,使用 extends 操作符。在 ExtractSurname<“Daniel”> 中,T 的值將等于 "Daniel"。
接下來我們應用 TypeScript 三元運算符,它類似于 JavaScript 三元運算符,但我們是在比較類型而不是實際數據。
我們知道我們的名字數組的格式是“<名> <姓>”,所以這里使用 infer 關鍵字從 T 中提取子類型。
在 ExtractSurname<“Daniel Craciun”> 中:
- infer Firstname = “Daniel”
- infer Surname = “Craciun”
最后,如果輸入滿足我們的“<名> <姓>”格式,返回 Surname 作為類型,否則返回 null。
好的,我們的 ExtractSurname 類型準備好了。
現在我們需要一個 Surname 類型來表示 names 中所有的姓氏。
type ExtractSurname<T extends string> = T extends `${infer Firstname} ${infer Surname}` ? Surname : null
const names = ["Daniel Craciun", "John Doe", "Harry Pigeon"] as const
type Surname = ExtractSurname<(typeof names)[number]>names 滿足 ExtractSurname 的格式 “*<名> <姓>*”。
我們使用 as const 將 names 的類型從字符串縮小到字面字符串數組。
這意味著我們轉換 names 的類型從 string 到:readonly [“Daniel Craciun”, “John Doe”, “Harry Pigeon”]。
參數 (typeof names)[number] 代表 names 中每個索引元素的類型:“Daniel Craciun” | “John Doe” | “Harry Pigeon”
最終,這是 Surname 的結果類型:type Surname = “Craciun” | “Doe” | “Pigeon”
最后一步是用下面的新函數 findNameUsingSurname 更新我們之前定義的 findName 函數:
// 接收一個實際的 `Surname` 而不是一般的字符串。
const findNameUsingSurname = (surname: Surname) => {
// 注意:我們需要后綴運算符 "!" 來斷言 "find" 函數不返回未定義的值。
return names.find((name) => name.includes(surname))!
}
// 唯一可接受的輸入:"Craciun", "Doe", "Pigeon" = 最大類型安全
const fullName = findNameUsingSurname("Craciun")
// 輸出:"Daniel Craciun"
console.log(fullName)而這里是 TypeScript 編譯器如我們所期待的那樣施展它的魔法:




























