首页 > 解决方案 > 在下游接口上保留基于字段内容的联合类型区分

问题描述

这是场景。

我有一个表示泛型元数据的接口Field(假设我们正在谈论MatFormField来自 Angular Material 的实例),它包含一个名称和一个类型,它是一个常见输入类型的列表。

export type FieldType = 'string' | 'number' | 'date' | 'datetime' | 'select';

export interface FieldMetadata {
  readonly type: FieldType;
  name: string;
}

有些Fields 只使用这些元数据就可以了,而另一些则需要额外的信息,因此需要接口扩展。然后我定义了一个联合类型,所以我可以根据“类型”字段获得正确的类型提示。

// Specific interface for select field
export interface SelectFieldInterface extends FieldMetadata {
  readonly type: 'select';
  options: string[];
}

// All other fields which doesn't need additional info
export interface GenericFieldInterface extends FieldMetadata {
  readonly type: Exclude<FieldType, 'select'>;
}

export type Field = GenericFieldInterface | SelectFieldInterface;

// Understands it's a GenericFieldInterface
const genericField1: Field = {
  type: 'number',
  name: ''
};

const genericField2: Field = {
  type: 'number',
  name: '',
  option: [] // <---- Type Error, unexpected 'options'
};

// Understands it's a SelectFieldInterface
const selectField1: Field = {
  type: 'select',
  name: '',
  options: []
};

const selectField2: Field = {
  type: 'select',
  name: '',
  // <----- Type Error, expected 'options'
};

在这里一切都按预期工作(但如果您知道更好的管理方法,请分享)。

当我想建立在这个抽象之上时,问题就出现了。

假设应该在这种联合类型之上构建一个新模型以保持区分。我希望这样的事情能够得到这样的结果。

export interface Model extends Field {
  additionalProperty: string;
}

但是在 TypeScript 中这是不可行的,你不能扩展联合类型。我通过定义一个接受所有Fields 类型的所有属性的中间“超类型”部分解决了这个问题。

我不得不在特定实现上省略类型属性,否则会出现与接受的类型值冲突的问题。

export type ExtendedField = FieldMetadata &
  Partial<Omit<GenericFieldInterface & SelectFieldInterface, 'type'>>;

但是这样我失去了两件事。

首先是各种实施的区别。

const genericField: Model = {
  type: 'number',
  name: '',
  additionalProperty: '',
  option: [] // <---- Doesn't fire Type Error for 'option' property, it should
};

const selectField: Model = {
  type: 'select',
  name: '',
  additionalProperty: '',
  // <---- Doesn't fire Type Error for missing 'option' property, it should
};

第二个是FieldModel签名之间的兼容性:我不能Model在签名要求的地方使用Field. 当然知道它们是兼容的,但是编译器不兼容,我不得不一直强制Model转换Field

function someFn(field: Field) {}

const selectField: Model = {
  type: 'select',
  name: '',
  additionalProperty: '',
  option: []
};

someFn(selectField) <------ Type Error
someFn(selectField as Field) <------ Works with casting

关于如何更好地代表这些约束的任何想法?我试图研究条件类型,但我不明白它们是否对我有用......

标签: typescript

解决方案


解决您的问题的最简单方法是使用带有交集的类型别名,添加您需要的额外属性。只要这些类型仅用于类型检查对象文字并且不必由类实现,它应该都能正常工作:

type Model = Field & {
    additionalProperty: string;
}
const genericField: Model = {
  type: 'number',
  name: '',
  additionalProperty: '',
  option: [] // err
};

const selectField: Model = { //err
  type: 'select',
  name: '',
  additionalProperty: '',
};

推荐阅读