La interfaz mecanografiada requiere que exista una de dos propiedades.

Resuelto Nix asked hace 8 años • 11 respuestas

Estoy intentando crear una interfaz que podría tener

export interface MenuItem {
  title: string;
  component?: any;
  click?: any;
  icon: string;
}
  1. ¿Hay alguna manera de exigir componento clickestablecer?
  2. ¿Hay alguna manera de exigir que no se puedan establecer ambas propiedades?
Nix avatar Nov 09 '16 22:11 Nix
Aceptado

Con la ayuda del Excludetipo que se agregó en TypeScript 2.8, se proporciona una forma generalizable de requerir al menos una de un conjunto de propiedades:

type RequireAtLeastOne<T, Keys extends keyof T = keyof T> =
    Pick<T, Exclude<keyof T, Keys>> 
    & {
        [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>
    }[Keys]

Y una forma parcial pero no absoluta de exigir que se proporcione uno y solo uno es:

type RequireOnlyOne<T, Keys extends keyof T = keyof T> =
    Pick<T, Exclude<keyof T, Keys>>
    & {
        [K in Keys]-?:
            Required<Pick<T, K>>
            & Partial<Record<Exclude<Keys, K>, undefined>>
    }[Keys]

Aquí hay un enlace al área de juegos de TypeScript que muestra ambos en acción.

La advertencia RequireOnlyOnees que TypeScript no siempre conoce en el momento de la compilación todas las propiedades que existirán en el tiempo de ejecución. Obviamente, RequireOnlyOneno puedo hacer nada para evitar propiedades adicionales que no conoce. Proporcioné un ejemplo de cómo RequireOnlyOnese pueden perder cosas al final del enlace del patio de juegos.

Una descripción general rápida de cómo funciona utilizando el siguiente ejemplo:

interface MenuItem {
  title: string;
  component?: number;
  click?: number;
  icon: string;
}

type ClickOrComponent = RequireAtLeastOne<MenuItem, 'click' | 'component'>
  1. Pick<T, Exclude<keyof T, Keys>>from RequireAtLeastOnecomes { title: string, icon: string}, que son las propiedades sin cambios de las claves no incluidas en'click' | 'component'

  2. { [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>> }[Keys]de RequireAtLeastOnese convierte

    { 
        component: Required<{ component?: number }> & { click?: number }, 
        click: Required<{ click?: number }> & { component?: number } 
    }[Keys]
    

    que se convierte

    {
        component: { component: number, click?: number },
        click: { click: number, component?: number }
    }['component' | 'click']
    

    que finalmente se convierte

    {component: number, click?: number} | {click: number, component?: number}
    
  3. La intersección de los pasos 1 y 2 anteriores

    { title: string, icon: string} 
    & 
    ({component: number, click?: number} | {click: number, component?: number})
    

    simplifica a

    { title: string, icon: string, component: number, click?: number} 
    | { title: string, icon: string, click: number, component?: number}
    
KPD avatar Apr 09 '2018 03:04 KPD

No con una única interfaz, ya que los tipos no tienen lógica condicional y no pueden depender unos de otros, pero puedes hacerlo dividiendo las interfaces:

export interface BaseMenuItem {
  title: string;
  icon: string;
}

export interface ComponentMenuItem extends BaseMenuItem {
  component: any;
}

export interface ClickMenuItem extends BaseMenuItem {
    click: any;
}

export type MenuItem = ComponentMenuItem | ClickMenuItem;
ssube avatar Nov 09 '2016 15:11 ssube

Hay una solución más sencilla. No es necesario depender de tipos condicionalesany complejos (ver respuesta) :

  1. ¿Hay alguna manera de requerir que se configure un componente o un clic? (Inclusivo OR)
type MenuItemOr = {
    title: string;
    icon: string;
} & ({ component: object } | { click: boolean }) 
// brackets are important here: "&" has precedence over "|"

let testOr: MenuItemOr;
testOr = { title: "t", icon: "i" } // error, none are set
testOr = { title: "t", icon: "i", component: {} } // ✔
testOr = { title: "t", icon: "i", click: true } // ✔
testOr = { title: "t", icon: "i", click: true, component: {} } // ✔

Un tipo de unión ( |) corresponde a inclusivo OR. Se cruza con las propiedades no condicionales.

Utilice el inoperador para limitar el valor a uno de los componentes:

if ("click" in testOr) testOr.click // works 
  1. ¿Hay alguna manera de exigir que no se puedan establecer ambas propiedades? (Exclusivo OR/ XOR)
type MenuItemXor = {
    title: string;
    icon: string;
} & (
        | { component: object; click?: never }
        | { component?: never; click: boolean }
    )

let testXor: MenuItemXor;
testXor = { title: "t", icon: "i" } // error, none are set
testXor = { title: "t", icon: "i", component: {} } // ✔
testXor = { title: "t", icon: "i", click: true } // ✔
testXor = { title: "t", icon: "i", click: true, component: {} } //error,both set

Básicamente se puede configurar uno component o , el otro nunca debe agregarse al mismo tiempo. TS puede hacer un tipo de unión discriminada a partir de , que corresponde a .clickMenuItemXorXOR

Esta XORcondición MenuItemXorno es posible con una respuesta aceptada .


Patio de juegos

1 Técnicamente, prop?: neverse resuelve en prop?: undefined, aunque el primero se utiliza a menudo a modo de ilustración.

ford04 avatar Mar 10 '2020 11:03 ford04

Una alternativa sin múltiples interfaces es

export type MenuItem = {
  title: string;
  component: any;
  icon: string;
} | {
  title: string;
  click: any;
  icon: string;
};

const item: MenuItem[] = [
  { title: "", icon: "", component: {} },
  { title: "", icon: "", click: "" },
  // Shouldn't this error out because it's passing a property that is not defined
  { title: "", icon: "", click: "", component: {} },
  // Does error out :)
  { title: "", icon: "" }
];

Hice una pregunta similar en Cómo crear un tipo parcial que requiere que se establezca una sola propiedad

Lo anterior podría simplificarse, pero puede que sea más fácil de leer o no.

export type MenuItem = {
  title: string;
  icon: string;
} & (
 {component: any} | {click: string}
)

Tenga en cuenta que ninguno de estos le impide agregar ambos porque TypeScript permite propiedades adicionales en objetos que usan Y/O Consulte https://github.com/Microsoft/TypeScript/issues/15447

Ruan Mendes avatar Jan 12 '2018 19:01 Ruan Mendes