簡體   English   中英

混合聯合類型、泛型和條件類型會導致意外的“類型不可分配給類型”錯誤

[英]Mixing union types, generics and conditional types causes unexpected "Type is not assignable to type" error

當在聯合類型中使用條件類型時,我遇到了類型推斷問題。

可能有一種更短的方法來證明這個問題,但我找不到一個......

在此Playground 鏈接中查看實際問題。

考慮以下Result<T> ,一個聯合類型,用於指示操作的成功或失敗(帶有可選的附加值,類型T )。 對於成功案例,我使用了條件類型SuccessResult<T> ,它解析為OKResultValueResult<T> (取決於結果是否還應帶有value ):

type Result<T = undefined> = SuccessResult<T> | ErrorResult;

interface OKResult {
    type: 'OK';
}
interface ValueResult<T> {
    type: 'OK';
    value: T;
}
interface ErrorResult {
    type: 'Error';
    error: any;
}
type SuccessResult<T = undefined> = T extends undefined ? OKResult : ValueResult<T>;

function isSuccess<T>(result: Result<T>): result is SuccessResult<T> {
    return result.type === 'OK';
}

讓我們用一個簡單的聯合類型來使用它:

type C1 = "A1" | "B1";
function makeC1(): C1 { return "A1" }
const c1: C1 = makeC1();
const c1Result: Result<C1> = { type: "OK", value: c1 }; // ALL IS GOOD

現在,而不是簡單的聯合類型C1 ,它只是"A1" | "B1" "A1" | "B1" ,讓我們以完全相同的方式使用聯合類型的復數值C2

type A2 = {
    type: 'A2';
}
type B2 = {
    type: 'B2';
}
type C2 = A2 | B2;
function makeC2(): C2 { return { type: "A2" } }
const c2: C2 = makeC2();
const c2Result: Result<C2> = { type: "OK", value: c2 }; // OH DEAR!

這會導致錯誤:

“C2”型不能分配給“B2”型。

“A2”型不能分配給“B2”型。

屬性“類型”的類型不兼容。

類型 '"A2"' 不能分配給類型 '"B2"'。

如果我從等式中刪除條件類型並將我的Result<T>定義為使用ValueResult<T>而不是SuccessResult<T>

type Result<T = undefined> = ValueResult<T> | ErrorResult;

......一切都恢復了,但我失去了發出毫無價值的成功信號的能力。 如果我無法在這種情況下使用可選類型,這將是一個可悲的回退。

我哪里做錯了? 如何在Result<T>聯合中使用SuccessResult<T> ,其中T本身是一個復雜的聯合類型?

游樂場鏈接

type Result<T = undefined> = SuccessResult<T> | ErrorResult;

需要是

type Result<T = undefined> = SuccessResult<T> | ErrorResult | ValueResult<T>;

然后它編譯。

干杯,邁克

不幸的是,指定函數的返回值是不夠的。 您需要顯式返回類型。 然后它編譯。

不起作用

function makeC2(): C2 {
    return {
        type: "A2"
    };
};

有效

function makeC2() {
    const x: C2 = {
        type: "A2"
    };
    return x;
};

游樂場鏈接

這是因為C2A2 | B2 A2 | B2類型而Result<C2>{type: 'OK'; value: A2} | {type: 'OK'; value: B2} | ErrorResult {type: 'OK'; value: A2} | {type: 'OK'; value: B2} | ErrorResult {type: 'OK'; value: A2} | {type: 'OK'; value: B2} | ErrorResult

所以問題歸結為{type: 'OK'; value: A2 | B2} {type: 'OK'; value: A2 | B2} {type: 'OK'; value: A2 | B2}{type: 'OK'; value: A2} | {type: 'OK'; value: B2} {type: 'OK'; value: A2} | {type: 'OK'; value: B2} {type: 'OK'; value: A2} | {type: 'OK'; value: B2} 這似乎是等效的,直到您將更多屬性添加到此類對象中。 TypeScript 不會將此視為特殊情況,而是考慮不等價的一般情況(想象更多屬性)。

讓我們考慮另一個例子{ x: "foo" | "bar" } { x: "foo" | "bar" }{ x: "foo" } | { x: "bar" } { x: "foo" } | { x: "bar" }

例如,考慮{ x: "foo" | "bar", y: string | number }是不正確的{ x: "foo" | "bar", y: string | number } { x: "foo" | "bar", y: string | number } { x: "foo" | "bar", y: string | number }等價於{ x: "foo", y: string } | { x: "bar", y: number } { x: "foo", y: string } | { x: "bar", y: number }因為第一種形式允許所有四種組合,而第二種形式只允許兩種特定的組合。

來源: https : //github.com/microsoft/TypeScript/issues/12052#issuecomment-258653766

正如在某些答案中已經提到的,問題在於您有A2B2 ,它們是兩種不同類型的對象,盡管在您的情況下它們是相同的(都具有屬性type ),但您可以輕松地將其他屬性附加到一個,但不是另一個,在這種情況下,如果該類型背后的對象實際上是正確的,則Result<C2>將無法區分,因為A2不能分配給B2 ,反之亦然。

您可以做的是(如果您的結果類型具有相同的屬性)創建某種基本類型,其中type實際上是一個通用聯合,它將解析為特定值之一,如下所示:

type BaseType<T extends 'A2' | 'B2'> = {
    type: T;
}

type A2 = BaseType<'A2'>
type B2 = BaseType<'B2'>
type C2 = A2 | B2;

function makeC2(): C2 { return { type: 'A2' } }
const c2 = makeC2();
const c2Result: Result<BaseType<'A2' | 'B2'>> = { type: "OK", value: c2 };

或者更簡單的版本

type BaseType = {
    type: 'A2' | 'B2';
}

function makeC2(): BaseType { return { type: 'A2' } }
const c2 = makeC2();
const c2Result: Result<BaseType> = { type: "OK", value: c2 };

這完全取決於您的情況以及您是否可以像這樣共享您的類型,或者它們是特定的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM