TypeScript:將聯合類型「縮窄」到其中一個成員
假設我有這個聯合類型:
type Foo = {tag: 'A', name: string} | {tag: 'B', value: number}
我正在嘗試編寫一個函數,該函數獲取一個標籤值('A' | 'B'),然後在一個 Foo 列表中進行一些特定業務的查找,並返回匹配的元素。就這個問題而言,我們可以將這個函數視為非常簡單的函數:
function findFirst(foos: Foo[], tag: 'A' | 'B') {
for (const foo of foos) {
if (foo.tag === tag) {
return foo
}
}
return undefined
}
假設我現在呼叫這個函數,並將 'A' 作為第二個參數傳入。很明顯,它要麼返回 undefined,要麼返回 {tag: 'A', name: string} 物件。在這種情況下,它根本不可能返回 {tag: 'B', value: number}。
不過,TSC 似乎無法推斷這一點。當寫出像以下的表達式時:
findFirst([{tag: 'A', name: 'X'}], 'A')?.name
TSC 出現錯誤 Property 'name' does not exist on type 'Foo'.。
我猜我需要透過明確定義 findFirst() 的回傳類型來幫助編譯器推斷正確的型別,但我不確定該怎麼做。
(遊樂場)
答案#1
你需要在函數中添加一個類型參數,以捕捉傳入的標籤的實際類型。然後,你需要使用 Extract 預定義條件類型來過濾 Foo 聯合類型:
function findFirst<T extends Foo['tag']>(foos: Foo[], tag: T) {
for (const foo of foos) {
if (foo.tag === tag) {
return foo as Extract<Foo, { tag: T }>
}
}
return undefined
}
遊樂場連結
答案#2
你可以使用函數重載來聲明結果,而不必使用類型轉換。
type FooA = {tag: 'A', name: string};
type FooB = {tag: 'B', value: number};
type Foo = FooA | FooB
function findFirst(foos: Foo[], tag: 'A'): FooA | undefined;
function findFirst(foos: Foo[], tag: 'B'): FooB | undefined;
function findFirst(foos: Foo[], tag: 'A' | 'B') {
for (const foo of foos) {
if (foo.tag === tag) {
return foo
}
}
return undefined
}
findFirst([{tag: 'A', name: 'X'}], 'A')?.name
findFirst([{tag: 'A', name: 'X'}], 'B')?.value
// TS Error as expected
findFirst([{tag: 'A', name: 'X'}], 'A')?.value
遊樂場連結
