8

当条件类型在联合类型中使用时,我遇到了类型推断的问题。

可能有更短的方法来证明这个问题,但我找不到......

在这个操场链接上查看实际问题。

考虑以下Result<T>,一个联合类型,用于指示操作的成功或失败(带有可选的附加值,类型为T)。对于成功案例,我使用了条件类型SuccessResult<T>,它解析为OKResultor 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它本身就是一个复杂的联合类型?

游乐场链接

4

4 回答 4

1

不幸的是,仅指定函数的返回值是不够的。您需要显式返回类型。然后它编译。

不起作用

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

有效

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

游乐场链接

于 2020-09-26T12:48:35.713 回答
1

发生这种情况是因为C2isA2 | B2类型 while Result<C2>is {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

于 2020-09-29T06:37:58.630 回答
1

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

需要是

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

然后它编译。

干杯,迈克

于 2020-09-22T10:51:00.750 回答
1

正如在一些答案中已经提到的那样,问题是您拥有A2and B2which 是两种不同类型的对象,尽管它们在您的情况下是相同的(都具有 property 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 };

这完全取决于您的情况以及您是否可以像这样共享您的类型,或者它们是特定的。

于 2020-09-29T08:20:23.623 回答