我正在尋找一種方法來區分聯合型別作為泛型引數并在物件的多個欄位上具有相同的型別
我的意思是:
type A = {
type: 'A',
someA: string
}
type B = {
type: 'B',
someB: string
}
type Union = A | B
type DatabaseUpdate<T extends Union> = {
before: T, // <--
after: T // <-- Those should be the same type, either both A, or both B
}
function test(update: DatabaseUpdate<Union>) {
if (update.after.type === 'A') {
console.log(update.before.someA) // Error
}
}
我嘗試根據 the 提取值,type因為我認為
type Test = Exclude<Union, { type: 'A' }> // this is B
作業正常。但它似乎仍然無法弄清楚。
type $ElementType<T extends {
[P in K & any]: any;
}, K extends keyof T | number> = T[K];
type DatabaseUpdate<
T extends Union,
K = $ElementType<T, 'type'>,
X = Extract<Union, { type: K }>
> = {
before: X,
after: X
}
function test(update: DatabaseUpdate<Union>) {
if (update.after.type === 'A') {
console.log(update.before.someA) // Error
}
}
理想情況下,我什至可以擁有第三個領域,例如 type
type DatabaseUpdate<
T extends Union,
K = $ElementType<T, 'type'>,
X = Extract<Union, { type: K }>
> = {
type: K,
before: X,
after: X
}
// So that I could do
function test(update: DatabaseUpdate<Union>) {
if (update.type === 'A') {
console.log(update.before.someA)
console.log(update.before.someA)
}
if (update.type === 'B') {
console.log(update.before.someB)
console.log(update.before.someB)
}
}
打字稿游樂場
uj5u.com熱心網友回復:
在 TypeScript 中,可區分聯合是具有可區分屬性的聯合型別;聯合的每個成員都必須有一個具有相同鍵名的屬性,并且至少在某些成員中,判別屬性值必須是文字型別或其他“單例”型別(恰好由一個值占據的型別),例如or 。nullundefined
如果您有一個u判別鍵為 的判別聯合型別k的值,則檢查判別屬性的值u.k將導致編譯器縮小 的明顯型別u。
現在,您的第一種方法將無法直接使用,因為 TypeScript 目前不支持“嵌套”可區分聯合。您只能使用單例型別來區分聯合;您不能使用其他可區分聯合型別來區分聯合。
{before: A, after: A} | {before: B, after: B}因此,像這樣的聯合型別不是可區分的聯合。既不是單例型別,before也不after是單例型別;他們是受歧視的工會。所以如果你檢查update.after.type,這只能用于縮小型別update.after。它對update自身的表觀型別沒有任何作用。
microsoft/TypeScript#18758有一個功能請求來支持嵌套的歧視聯合,但尚不清楚何時或是否會實施。
有一些方法可以撰寫用戶定義的型別保護函式,可以給出類似的結果,但不會像撰寫 那樣簡單if (update.after.type === "A");它最終會看起來像if (nestedDiscriminate(update, ["after", "type"], "A")). 我不會討論這個,因為這里還有另一種方法。
如果您定義DatabaseUpdate<Union>結果型別是具有判別屬性的聯合,那么它將是一個有區別的聯合。所以添加一個type欄位是一個很好的方法。
不幸的是,您遇到了麻煩,因為您DatabaseUpdate<Union>對此進行了評估:
type DUU = DatabaseUpdate<Union>;
/* type DUU = {
type: "A" | "B";
before: A | B;
after: A | B;
}
*/
這根本不是工會,因此不是歧視工會。每個屬性都是一個聯合,但聯合物件與物件聯合不同。您可以看到差異,因為您的型別允許這樣的分配:
const oops: DUU = {
type: "A",
before: { type: "A", someA: "okay" },
after: { type: "B", someB: "uh oh" }
}
這是有效的;該type物業是"A",這是一個有效的"A" | "B",而after屬性是B,這是一個有效A | B。這個物件型別的每個屬性都是一個獨立的聯合型別;知道type屬性的值并不意味著關于before或after屬性。
我們希望DatabaseUpdate<Union>自己成為一個工會。一種方法是創建DatabaseUpdate<T>一個分布條件型別,這意味著DatabaseUpdate<A | B>將評估為DatabaseUpdate<A> | DatabaseUpdate<B>(我們在 中跨聯合分布DatabaseUpdate<T>型別T):
type DatabaseUpdate<T> = T extends Union ? {
type: T['type']
before: T,
after: T
} : never;
在評估型別之前,T extends Union ? ... : never構造導致T被拆分為其聯合成分,然后將輸出重新聯合在一起。
(我還使用簡化DatabaseUpdate來使用直接索引訪問型別 T['type']而不是最終等效的$ElementType<T, 'type'>)。
現在讓我們評估DatabaseUpdate<Union>:
type DUU = DatabaseUpdate<Union>
/* type DUU = {
type: "A";
before: A;
after: A;
} | {
type: "B";
before: B;
after: B;
} */
現在這是一個聯合型別,并且type是一個判別屬性。突然一切正常:
function test(update: DatabaseUpdate<Union>) {
if (update.type === 'A') {
console.log(update.before.someA) // okay
console.log(update.after.someA) // okay
} else {
console.log(update.before.someB) // okay
console.log(update.after.someB) // okay
}
}
Playground 鏈接到代碼
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/377277.html
標籤:打字稿
