Tengo varios objetos Item y un método que los procesa. El problema es que algunos elementos podrían tener ciertas propiedades que otros no.

type ItemType = 'ApiGateway' | 'ApiGatewayEndpoint' | 'ApiGatewayMethod';

export default interface Item {
    ref?: string;
    position: Position;
    type: ItemType;
    children?: Item[];
}

function process(item: Item) {...}

Ahora, supongamos que quiero crear una interfaz separada ApiGatewayMethodItem (que se extiende Item) que tiene una propiedad de cadena adicional llamada 'método' que representa si se trata de GET o POST u otra cosa. ¿Hay alguna manera de que pueda escribirlo de tal manera que tan pronto como escriba process({type: 'ApiGatewayMethod'}) tsc empiece a quejarse de la propiedad faltante method? Entiendo que TS tiene un soporte bastante bueno para los "tipos condicionales", pero no los he usado antes y me cuesta mucho entenderlos ...

Entonces digamos que tengo la interfaz

interface ApiGatewayMethodItem extends Omit<Item, 'type'> {
    type: 'ApiGatewayMethod';
    method: string;
}

Ahora cuando llamo a la función process, necesito que el compilador se queje de que falta la propiedad method cuando no la especifico, pero especifico el tipo ApiGatewayMethod

0
Kamil Janowski 31 may. 2020 a las 12:33

3 respuestas

La mejor respuesta

Puede definir su tipo de elemento solo con los 2 ItemTypes que no tienen la propiedad 'método'. Luego agregue la propiedad 'método' solo al tipo 'ApiGatewayMethodItem'

type ItemType = 'ApiGateway' | 'ApiGatewayEndpoint';

export default interface DefaultItem {
    ref?: string;
    position: Position;
    type: ItemType;
    children?: DefaultItem[];
}

interface ApiGatewayMethodItem extends Omit<DefaultItem, 'type'> {
    type: 'ApiGatewayMethod';
    method: string;
}

type Item = DefaultItem | ApiGatewayMethodItem;

function process(item: Item) {

}

process ({
    type: 'ApiGatewayMethod',
    position: null
})
1
Matthias Gwiozda 31 may. 2020 a las 10:14

Se podría solucionar esto mediante el uso de sindicatos discrimated. De esta forma, el compilador puede determinar qué miembros están disponibles en el campo type.

type ItemTypeWithoutMethod = 'ApiGateway' | 'ApiGatewayEndpoint' | 'ApiGatewayMethod';
type ItemTypeWithMethod = "ApiGatewayMethodItem";

interface ItemBase {
    ref?: string;
    position: Position;
    children?: Item[];
}

interface ItemWithoutMethod extends ItemBase {
  type: ItemTypeWithoutMethod
}

interface ItemWithMethod extends ItemBase {
    type: ItemTypeWithMethod;
    method: string;
}

export type Item = ItemWithMethod | ItemWithoutMethod;

function doSomethingWithItem(item: Item) {
  if (item.type == "ApiGatewayMethodItem") {
    console.log(item.method);
  }
}

Enlace de juegos

0
Christian Held 31 may. 2020 a las 10:01

Puede convertir Item en un tipo de unión y definir una interfaz que contenga las propiedades comunes a todos los elementos:

export default interface CommonItemProperties {
    ref?: string;
    position: Position;
    children?: Item[];
}

interface ApiGatewayMethodItem extends CommonItemProperties {
    type: 'ApiGatewayMethod';
    method: 'GET' | 'POST';
}

interface ApiGatewayEndpointItem extends CommonItemProperties {
  type: 'ApiGatewayEndpoint';
  path: string;
}

type Item = ApiGatewayEndpointItem | ApiGatewayMethodItem;

function process(item: Item) {
  switch (item.type) {
    case ('ApiGatewayMethod'):
      return item.method; // this is not a type error - typescript 'knows' the item is a ApiGatewayMethodItem
    case ('ApiGatewayEndpoint'):
      return item.path;   // this is not a type error - typescript 'knows' the item is a ApiGatewayEndpointItem
  }
}

La declaración switch es solo un ejemplo de cómo el mecanografiado puede determinar el tipo de elemento que ha pasado en base a una verificación contra type.

1
OliverRadini 31 may. 2020 a las 09:59