web-dev-qa-db-fra.com

TypeScript: supprimer la clé du type / type de soustraction

Je veux définir un type générique ExcludeCart<T> qui est essentiellement T mais avec une clé donnée (dans mon cas, cart) supprimée. Ainsi, par exemple, ExcludeCart<{foo: number, bar: string, cart: number}> serait {foo: number, bar: string}. Existe-t-il un moyen de le faire en TypeScript?

Voici pourquoi je veux le faire, au cas où j'aboyer le mauvais arbre: je convertis une base de code JavaScript existante en TypeScript, qui contient une fonction de décorateur appelée cartify qui prend un React classe de composants Inner et retourne une autre classe de composants Wrapper.

Inner devrait prendre un cart prop, et zéro ou plusieurs autres props. Wrapper accepte un cartClient prop (qui est utilisé pour générer le cart prop à passer à Inner), et tout accessoire que Inner accepte, saufcart.

En d'autres termes, une fois que je peux comprendre comment définir ExcludeCart, je veux le faire avec:

function cartify<P extends {cart: any}>(Inner: ComponentClass<P>) : ComponentClass<ExcludeCart<P> & {cartClient: any}>
20
Leigh Brenecki

Bien qu'il n'y ait pas de type de soustraction intégré, vous pouvez actuellement le pirater dans:

type Sub0<
    O extends string,
    D extends string,
> = {[K in O]: (Record<D, never> & Record<string, K>)[K]}

type Sub<
    O extends string,
    D extends string,
    // issue 16018
    Foo extends Sub0<O, D> = Sub0<O, D>
> = Foo[O]

type Omit<
    O,
    D extends string,
    // issue 16018
    Foo extends Sub0<keyof O, D> = Sub0<keyof O, D>
> = Pick<O, Foo[keyof O]>

Dans le cas de la question, vous feriez:

type ExcludeCart<T> = Omit<T, 'cart'>

Avec TypeScript> = 2.6, vous pouvez le simplifier pour:

/**
 * for literal unions
 * @example Sub<'Y' | 'X', 'X'> // === 'Y'
 */
export type Sub<
    O extends string,
    D extends string
    > = {[K in O]: (Record<D, never> & Record<string, K>)[K]}[O]

/**
 * Remove the keys represented by the string union type D from the object type O.
 *
 * @example Omit<{a: number, b: string}, 'a'> // === {b: string}
 * @example Omit<{a: number, b: string}, keyof {a: number}> // === {b: string}
 */
export type Omit<O, D extends string> = Pick<O, Sub<keyof O, D>>

testez-le sur le terrain de je

6
Adrian Leonhard

Depuis TypeScript 2.8 et l'introduction de Exclude, il est maintenant possible d'écrire ceci comme suit:

type Without<T, K> = {
    [L in Exclude<keyof T, K>]: T[L]
};

Ou encore, et de manière plus concise, comme:

type Without<T, K> = Pick<T, Exclude<keyof T, K>>;

Pour votre usage, vous pouvez maintenant écrire ce qui suit:

type ExcludeCart<T> = Without<T, "cart">;
21
alter igel

Mise à jour: Voir réponse d'Adrian ci-dessus pour une solution à cette question. J'ai laissé ma réponse ici car elle contient toujours des liens utiles.


Il existe diverses anciennes demandes pour cette fonctionnalité ( types "outersection" , types de soustraction ), mais aucune n'a vraiment progressé.

Récemment, avec l'ajout de types mappés, j'ai à nouveau posé des questions à ce sujet, et Anders a dit que bien qu'il ne soit pas prévu de créer un opérateur de type de soustraction général , une version plus limitée pourrait être implémentée, ressemblant probablement à quelque chose comme - cette proposition .

Je me suis personnellement retrouvé dans des situations assez similaires lorsque vous travaillez avec React, et malheureusement je n'ai pas pu trouver de bonne solution. Dans un cas simple, vous pouvez vous en sortir avec quelque chose comme:

interface BaseProps {
    foo: number;
    bar: number;
}

interface Inner extends BaseProps {
    cart: Cart;
}

interface Wrapper extends BaseProps {
    cartClient: Client;
}

mais je trouve presque que c'est un abus sémantique du mot clé extends. Et bien sûr, si vous ne contrôlez pas les saisies de Inner ou BaseProps, cela ne fonctionnera pas.

1
JKillian