Typescript - 使用泛型擴展具有聯合類型屬性的介面
我有一個像這樣的介面
interface Test {
values: string[] | number[] | Date[]
// more properties
}
我想創建另一個介面,使類型具有特定性。我正在嘗試的是:
interface TestWithType<T extends string | number | Date> extends Test {
values: T[];
}
但是這會失敗,並顯示錯誤訊息 Type 'string' is not assignable to type 'Date'。
這個的正確語法是什麼?如果屬性 values 不是陣列,這將可以正常運作
interface Test {
values: string | number | Date
}
interface TestWithType<T extends string | number | Date> extends Test {
values: T;
}
答案#1
正如你所注意到的,你不會通過將類型為 string | number | Date 的元素組成陣列來得到 string[] | number[] | Date[]。並且由於 (string | number | Date)[] 比 string[] | number[] | Date[] 更廣,因此會產生編譯器錯誤,因為你不能擴寬子類型的屬性。
@MattMcCutchen 的回答提供了一些處理這個問題的方法。這裡有另一種方法:
如果你有一個像 string | number | Date 的聯合類型,並且想要以程式方式將其轉換為像 string[] | number[] | Date[] 的數組聯合類型,你可以使用分配型條件類型:
type DistributeArray<T> = T extends any ? T[] : never;
然後你可以用 DistributeArray 來定義 TestWithType:
// no error:
interface TestWithType<T extends string | number | Date> extends Test {
values: DistributeArray<T>;
}
並確認它的行為符合你的預期:
declare const testWithString: TestWithType<string>
testWithString.values; // string[]
declare const testWithDate: TestWithType<Date>
testWithDate.values; // Date[]
declare const testWithStringOrNumber: TestWithType<string | number>
testWithStringOrNumber.values; // string[] | number[]
希望這對你有幫助。祝你好運!
編輯:
作為相關問題,有沒有辦法禁止將聯合類型傳遞給泛型?(例如要求最多只能指定 string、number 或 date 中的一種)
是的,那是可能的,但這需要以一種讓我感到不舒服的方式濫用類型系統。我建議如果不必要的話不要這麼做。這是內容如下:
type DistributeArray<T> = T extends any ? T[] : never;
type NotAUnion<T> = [T] extends [infer U] ? U extends any ?
T extends U ? unknown : never : never : never
type ErrorMsg = "NO UNIONS ALLOWED, PAL"
interface TestWithType<T extends (
unknown extends NotAUnion<T> ? string | number | Date : ErrorMsg
)> extends Test {
values: DistributeArray<T>
}
declare const testWithString: TestWithType<string> // okay
declare const testWithDate: TestWithType<Date> // okay
declare const testWithStringOrNumber: TestWithType<string | number> // error:
// 'string | number' does not satisfy the constraint '"NO UNIONS ALLOWED, PAL"'.
類型 NotAUnion<T> 如果 T 不是聯合類型會評估為 unknown(一個頂類型)……否則它會評估為 never(一個底類型)。然後,在 TestWithType 中,類型 T 被限制為 unknown extends NotAUnion<T> ? string | number | Date : ErrorMsg,幾乎是在觸碰循環引用規則的邊界。如果你傳入非聯合類型作為 T,它像之前一樣變成 T extends string | number | Date。如果你傳入聯合類型,它會變成 T extends ErrorMsg,一個字面字串類型。由於聯合類型永遠不會擴展一個字面字串,所以會失敗……而你得到的錯誤訊息會包含 ErrorMsg,希望這對開發者理解發生了什麼足夠。它能運作,但非常非常危險。 不過,這是你要求的。
再次祝你好運!
答案#2
兩種可能的解決方案,使用查找類型或條件類型:
enum TestType { STRING, NUMBER, DATE }
interface TestTypeMapping {
[TestType.STRING]: string[];
[TestType.NUMBER]: number[];
[TestType.DATE]: Date[];
}
interface TestWithType1<T extends TestType> extends Test {
values: TestTypeMapping[T];
}
type TypeToArrayType<T> =
T extends string ? string[] :
T extends number ? number[] :
T extends Date ? Date[] :
never[];
interface TestWithType2<T extends string | number | Date> extends Test {
values: TypeToArrayType<T>;
}
