web-dev-qa-db-fra.com

Est-il possible de combiner des membres de plusieurs types dans une annotation TypeScript?

Il semble que ce que j'essaie de faire ne soit pas possible, mais j'espère vraiment que c'est le cas.

J'ai essentiellement deux interfaces et je souhaite annoter un paramètre de fonction unique en combinant les deux.

interface ClientRequest {
    userId:     number
    sessionKey: string
}

interface Coords {
    lat:  number
    long: number
}

Et puis, dans la fonction, je veux faire quelque chose comme ceci:

function(data: ClientRequest&Coords) { ... }

Ainsi, mon objet 'data' pourrait contenir tous les membres des deux types.

J'ai vu quelque chose référencé dans un aperçu de spec , sous "Combining Types 'Members", mais il semble que cela ne soit pas encore arrivé.

Si ce n'est pas possible, ma solution pourrait ressembler à ceci:

interface ClientRequest<T> {
    userId:     number
    sessionKey: string
    data?:       T
}

function(data: ClientRequest<Coords>) { ... }

Ce qui fonctionnerait dans ce cas, bien que ce ne soit pas aussi dynamique que je le souhaiterais. J'aimerais vraiment pouvoir combiner plusieurs types (2+) dans l'annotation elle-même:

function(data: TypeA&TypeB&TypeC) { ... }

J'imagine que la solution classique consiste à définir un type qui étend ces types, bien que cela semble moins flexible. Si je veux ajouter un type, je devrais soit

  • (a) revenir à la déclaration et la récrire, ou
  • (b) créer une interface entièrement nouvelle. Pas sûr que je suis d'accord avec les frais généraux supplémentaires.

Les experts de TypeScript souhaitent-ils me diriger dans la bonne direction?

17
Joshua

La réponse spécifique à votre question est la suivante: non, il n'y a pas une seule annotation en ligne pour indiquer les types combinés ou étendus. 

La meilleure pratique pour le problème que vous essayez de résoudre serait de créer un troisième type qui étendrait les deux autres.

interface IClientRequestAndCoords extends IClientRequest, ICoords {} 

function(data: IClientRequestAndCoords) 

MISE &AGRAVE; JOUR2018-10-30

TypeScript a maintenant des intersections de types. Donc, vous pouvez maintenant faire simplement:

interface ClientRequest {
  userId:     number
  sessionKey: string
}

interface Coords {
  lat:  number
  long: number
}

function log(data: ClientRequest & Coords) { 
  console.log(
    data.userId,
    data.sessionKey,
    data.lat,
    data.long
  );
}
25
Martin

La réponse d'interface est une méthode raisonnablement élégante de combinaison des deux structures, mais vous indiquez que vous souhaitez savoir s'il est possible de combiner le type dans le cadre d'une annotation.

Une note sur les interfaces

J'ai fourni quelques descriptions de quelques fonctionnalités liées à votre question, mais tout d'abord, je dirais que si vous êtes rebuté de la solution d'interface parce que vous pensez qu'il vous faudra créer une interface ICoords (comme dans votre question, cela ressemble davantage à une classe) - restez tranquille - car une interface peut aussi étendre une classe:

// Interface extending an interface and a class
interface IClientRequestAndCoords extends IClientRequest, Coords {} 

L'interface fusionnera même les propriétés tant qu'elles auront le même nom et le même type. (Par exemple, s'ils ont tous deux déclaré une propriété x: string.

Voici des notes sur les autres fonctions d'annotation auxquelles vous faites allusion.

Types d'Union

La spécification que vous avez pu lire est le type d'union, qui ressemble à ceci:

var x: IClientRequest | Coords;

Mais cela garantit uniquement que x est l’un ou l’autre, et non une combinaison des deux. Votre syntaxe d'un type fusionné IClientRequest & Coords ne figure pas sur la feuille de route pour autant que je sache.

function go(data: IClientRequest | Coords) {
    var a = data[0]; // IClientRequest
    var b = data[1]; // Coords
}

// Allowed (even though it doesn't supply Coords data
go(myClientRequest);

// Allowed (even though it doesn't supply IClientRequest data
go (myCoords);

Cela ne fait pas non plus partie de la version actuelle, mais arrive plus tard.

Types de tuple

Une autre partie possible de la spécification que vous avez peut-être vue concerne les types Tuple:

var x: [IClientRequest, Coords];

Mais cela changerait la forme des données, qui deviendraient une structure, deviendraient un tableau où l'élément 0 serait une IClientRequest et que l'élément 1 serait une Coords.

function go(data: [IClientRequest, Coords]) {
    var a = data[0]; // ClientRequest
    var b = data[1]; // Coords
}

go([myClientRequest, myCoords]);

Uber-Annotation

Et enfin, si vous ne voulez vraiment pas créer une interface fusionnée, vous pouvez simplement utiliser une uber-annotation:

function go(data: { userId:number; sessionKey: string; x: number; y: number; } ) {

}
3
Fenton

J'ai vu quelque chose référencé dans un aperçu de spec , sous "Combining Types 'Members", mais il semble que cela ne soit pas encore arrivé.

Je pense que vous seriez plus intéressé par les types intersection (et non pas union). La différence est que l'objet transmis doit prendre en charge all des propriétés et non one of

Problème Github: https://github.com/Microsoft/TypeScript/issues/1256#issuecomment-64533287

1
basarat

C'est très possible si vous utilisez ES6 Object.assign

interface ClientRequest {
    userId:     number
    sessionKey: string
}

interface Coords {
    lat:  number
    long: number
}

type Combined = ClientRequest & Coords;

Ensuite, vous pouvez définir vos données comme ceci:

let myData = Object.assign({},
    <ClientRequest> {
        userId: 10,
        sessionKey: "gjk23h872ty3h82g2ghfp928ge"
    },
    <Coords> {
        lat: -23,
        long: 52
    }
);

Enfin, appelez la fonction:

function myFunc(data: Combined) { ... }
myFunc(myData);

Voir cette autre question pour encore plus de moyens d'y parvenir:

Comment puis-je fusionner des propriétés de deux objets JavaScript dynamiquement?

1
smac89