TypeScript:在不强制转换的情况下从有区别的联合创建一个新对象

TypeScript: Creating a new object from a discriminated union without casting

Link to TS playground of the following Q

假设我有以下类型和变量声明:

interface TypeA {
    id: string;
    type: 'A',
    fields: {
        thing1: string;
        thing2: number;
    }
}

const defaultA = {
    thing1: 'hey',
    thing2: 123
}

interface TypeB {
    id: string;
    type: 'B',
    fields: {
        thing3: boolean;
        thing4: number;
    }
}

const defaultB = {
    thing3: true,
    thing4: 456
}

const defaultFields = {
    A: defaultA,
    B: defaultB,
}

type AnyType = TypeA | TypeB

正在尝试通过以下方式创建新的 AnyType

const createNewThing = (type: TypeA['type'] | TypeB['type']): AnyType => {
    return {
        id: 'new id',
        type,
        fields: defaultFields[type],
    }
}

抛出 TS 错误:

Type '{ id: string; type: "A" | "B"; fields: { thing1: string; thing2: number; } | { thing3: boolean; thing4: number; }; }' is not assignable to type 'AnyType'.
  Type '{ id: string; type: "A" | "B"; fields: { thing1: string; thing2: number; } | { thing3: boolean; thing4: number; }; }' is not assignable to type 'TypeB'.
    Types of property 'type' are incompatible.
      Type '"A" | "B"' is not assignable to type '"B"'.
        Type '"A"' is not assignable to type '"B"'.(2322)

为了解决这个问题,我可以将正在创建的对象转换为 AnyType:

const createNewThing = (type: TypeA['type'] | TypeB['type']): AnyType => {
    return {
        id: 'new id',
        type,
        fields: defaultFields[type],
    } as AnyType;
}

但我宁愿尽可能避免转换。除了通过转换之外,还有其他方法可以解决这个问题吗?

这实际上是 typescript 的一个限制:discriminated unions 仅适用于顶级字段。

在您发布的示例中,thing1 thing2 thing3 thing4 都嵌套在 fields 下。如果您将它们提升了一个级别,您的示例应该有效。

有关更多信息,请参阅此 blog post