[英]Typescript generics + conditional types is not assignable to type error
[英]Mixing union types, generics and conditional types causes unexpected "Type is not assignable to type" error
當在聯合類型中使用條件類型時,我遇到了類型推斷問題。
可能有一種更短的方法來證明這個問題,但我找不到一個......
在此Playground 鏈接中查看實際問題。
考慮以下Result<T>
,一個聯合類型,用於指示操作的成功或失敗(帶有可選的附加值,類型T
)。 對於成功案例,我使用了條件類型SuccessResult<T>
,它解析為OKResult
或ValueResult<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;
};
這是因為C2
是A2 | 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
正如在某些答案中已經提到的,問題在於您有A2
和B2
,它們是兩種不同類型的對象,盡管在您的情況下它們是相同的(都具有屬性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.