带有映射类型的 Typescript 类型推断无法正常工作

Typescript type inference with mapped types does not work correctly

我正在尝试使用 TypeScript 在我的应用程序中提供类型安全。

我有一些带有必填“值”字段的类型。他们还有一个“类型”属性,我用它来缩小类型。 然后我创建了这种类型的联合类型。我可以通过检查类型字段来检查其中的内容。

然后我想创建另一种类型,它将具有第一个联合类型的所有字段,但值字段是可选的(可以未定义)。不幸的是,这种类型的行为不符合我的要求。

这是我的代码的简化版本,用于演示问题:

type First = {
  type: 'FIRST';
  value: number;
  uniqueFieldInFirst: string;
};

type Second = {
  type: 'SECOND';
  value: string;
  uniqueFieldInSecond: string;
};

type FirstOrSecond = First | Second;

type Opt<T extends FirstOrSecond>
  = Omit<T, 'value'> // This way we add all fields from FirstOrSecond type expect the 'value'
  & Partial<T>; // And now we all remaining fields (only value, but with "?")

const example: Opt<First> = {
  type: 'FIRST',
  value: 1234,
  uniqueFieldInFirst: 'abc'
}; // works

const another: Opt<Second> = {
  type: 'SECOND',
  value: '1234',
  uniqueFieldInSecond: 'abc'
}; // works

const oneMore: Opt<Second> = {
  type: 'SECOND',
  value: undefined,
  uniqueFieldInSecond: 'abc'
}; // works

const secondTest = (optional: Opt<FirstOrSecond>) => {
  if (optional.value !== undefined) {
    const firstOrSecond: FirstOrSecond = optional;
    // here comes the error:
    // TS2322: Type 'Opt<FirstOrSecond>' is not assignable to type 'FirstOrSecond'.
    // Type 'Omit<FirstOrSecond, "value"> & Partial<First>' is not assignable to type 'FirstOrSecond'.
    // Type 'Omit<FirstOrSecond, "value"> & Partial<First>' is not assignable to type 'First'.
    // Types of property 'value' are incompatible.
    // Type 'number | undefined' is not assignable to type 'number'.
    // Type 'undefined' is not assignable to type 'number'.
  }
};

我的目标是创建这样的工作函数:

function mapToFirstOrSecond<T extends FirstOrSecond>(optional: Opt<T>): T | undefined {
  if (optional.value !== undefined) {
    return optional as T;
  }
  return undefined;
}

但是没有转换就不行。

您可以使用用户定义的类型保护来确保 Typescript 编译器使用正确的类型:

function isFirstOrSecond(optional: any): optional is FirstOrSecond {
    return optional.value !== undefined;
}

然后:

if (isFirstOrSecond(optional)) {
    // ...
}