我遇到了类型推断问题,特别是在联合类型中使用条件类型时。
可能有一种较短的方法来演示此问题,但是我找不到一个...
在此playground link上查看问题所在。
请考虑以下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"
(即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;
...一切再次正常,但是我失去了表示无价值成功的能力。如果在这种情况下无法使可选类型正常工作,那将是一个可悲的后退。
我哪里出错了?在SuccessResult<T>
本身是复杂的联合类型的情况下,如何在Result<T>
联合中使用T
?
答案 0 :(得分:1)
type Result<T = undefined> = SuccessResult<T> | ErrorResult;
需要成为
type Result<T = undefined> = SuccessResult<T> | ErrorResult | ValueResult<T>;
然后编译。
干杯,迈克
答案 1 :(得分:1)
不幸的是,仅指定函数的返回值还不够。您需要显式返回类型。然后编译。
此无效
function makeC2(): C2 {
return {
type: "A2"
};
};
此有效
function makeC2() {
const x: C2 = {
type: "A2"
};
return x;
};
答案 2 :(得分:1)
之所以会这样,是因为C2
是A2 | B2
类型,而Result<C2>
是
{type: 'OK'; value: A2} | {type: 'OK'; value: B2} | ErrorResult
。
因此,问题归结为{type: 'OK'; value: A2 | B2}
和{type: 'OK'; value: A2} | {type: 'OK'; value: B2}
的等效性。在您向此类对象添加更多属性之前,这似乎是等效的。 TypeScript不会将其视为特殊情况,而是考虑不等效的一般情况(想象更多属性)。
让我们考虑另一个示例:{ x: "foo" | "bar" }
和{ x: "foo" } | { x: "bar" }
。
例如,认为
{ x: "foo" | "bar", y: string | number }
等同于{ x: "foo", y: string } | { x: "bar", y: number }
是不正确的,因为第一种形式允许所有四种组合,而第二种形式仅允许两种特定的组合。
来源:https://github.com/microsoft/TypeScript/issues/12052#issuecomment-258653766
答案 3 :(得分:1)
正如某些答案中已经提到的那样,问题在于您有A2
和B2
这两种不同类型的对象,尽管它们在您的情况下是相同的(两者都具有属性{ {1}}),您可以轻松地将其他属性附加到一个属性,而不是其他属性,在这种情况下,如果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 };
这取决于您的情况以及是否可以共享这样的类型,或者它们是否特定。