Une interface est une forme d’un objet. Un objet JavaScript standard est une carte de paires key:value
. Les clés des objets JavaScript sont dans presque tous les cas des chaînes de caractères et leurs valeurs sont toutes les valeurs JavaScript prises en charge (primitives ou abstraites).
Une interface indique au compilateur TypeScript les noms de propriétés qu’un objet peut avoir et leurs types de valeurs correspondants. Par conséquent, l’interface est un type et est un type abstrait puisqu’elle est composée de types primitifs.
Lorsque nous définissons un objet avec des propriétés (clés) et des valeurs, TypeScript crée une interface implicite en regardant les noms de propriétés et le type de données de leurs valeurs dans l’objet. Cela se produit en raison de l’inférence de type.
Dans l’exemple ci-dessus, nous avons créé un objet student
avec des champs firstName
, lastName
, age
et getSalary
et nous lui avons attribué certaines valeurs initiales. En utilisant ces informations, TypeScript crée un type d’interface implicite pour student
.
{
firstName: string;
lastName: string;
age: number;
getSalary: (base: number) => number;
}
Une interface est comme un objet mais elle ne contient que les informations sur les propriétés de l’objet et leurs types. Nous pouvons également créer un type d’interface et lui donner un nom afin de l’utiliser pour annoter les valeurs des objets mais ici, cette interface n’a pas de nom puisqu’elle a été créée implicitement. Vous pouvez comparer cela avec le type de fonction dans la leçon précédente qui a été créé implicitement au début et ensuite nous avons créé un type de fonction explicitement en utilisant l’alias de type.
Essayons de jouer avec les propriétés de l’objet après qu’il ait été défini.
Comme vous pouvez le voir dans l’exemple ci-dessus, TypeScript se souvient de la forme d’un objet puisque le type de ross
est l’interface implicite. Si nous essayons de remplacer la valeur d’une propriété par une valeur de type différent de ce qui est spécifié dans l’interface ou si nous essayons d’ajouter une nouvelle propriété qui n’est pas spécifiée dans l’interface, le compilateur TypeScript ne compilera pas le programme.
Si vous voulez qu’un objet ait fondamentalement n’importe quelle propriété, alors vous pouvez marquer explicitement une valeur any
et le compilateur TypeScript ne déduira pas le type à partir de la valeur d’objet assignée. Il y a d’autres meilleures façons d’obtenir exactement cela et nous allons les passer en revue dans cet article.
Déclaration d’interface
Bien que l’interface implicite que nous avons vue jusqu’à présent soit techniquement un type, mais elle n’a pas été définie explicitement. Comme discuté, une interface n’est rien d’autre que la forme qu’un objet peut prendre. Si vous avez une fonction qui accepte un argument qui devrait être un objet mais d’une forme particulière, alors nous devons annoter cet argument (paramètre) avec un type d’interface.
Dans l’exemple ci-dessus, nous avons défini une fonction getPersonInfo
qui accepte un argument objet qui a firstName
, lastName
, age
et getSalary
champs de types de données spécifiés. Remarquez que nous avons utilisé un objet qui contient des noms de propriétés et leurs types correspondants comme type en utilisant l’annotation :<type>
. C’est un exemple d’interface anonyme puisque l’interface n’a pas de nom, elle a été utilisée en ligne.
Tout cela semble un peu compliqué à gérer. Si l’objet ross
devient plus compliqué et qu’il doit être utilisé à plusieurs endroits, TypeScript semble juste une chose que vous avez aimé au départ, mais maintenant juste une chose difficile à gérer. Pour résoudre ce problème, nous définissons un type d’interface en utilisant le mot-clé interface
.
Dans l’exemple ci-dessus, nous avons défini une interface Person
qui décrit la forme d’un objet, mais cette fois, nous avons un nom que nous pouvons utiliser pour faire référence à ce type. Nous avons utilisé ce type pour annoter la variable ross
ainsi que l’argument person
de la fonction getPersonIfo
. Cela informera TypeScript de valider ces entités par rapport à la forme de Person
.
Pourquoi utiliser une interface?
Le type interface peut être important pour faire respecter une forme particulière. Typiquement, en JavaScript, nous mettons une foi aveugle au moment de l’exécution qu’un objet contiendra toujours une propriété particulière et que cette propriété aura toujours une valeur d’un type particulier comme {age: 21, ...}
à titre d’exemple.
Lorsque nous commençons réellement à effectuer des opérations sur cette propriété sans vérifier d’abord si cette propriété existe sur l’objet ou si sa valeur est ce que nous attendions, les choses peuvent mal tourner et cela peut laisser votre application inutilisable par la suite. Par exemple, {age: '21', ...}
, ici age
la valeur est un string
.
Les interfaces fournissent un mécanisme sûr pour traiter de tels scénarios au moment de la compilation. Si vous utilisez accidentellement une propriété sur un objet qui n’existe pas ou si vous utilisez la valeur d’une propriété dans l’opération illégale, le compilateur TypeScript ne compilera pas votre programme. Voyons un exemple.
Dans l’exemple ci-dessus, nous essayons d’utiliser la propriété name
de l’argument _student
à l’intérieur de la fonction printStudent
. Comme l’argument _student
est un type d’interface Student
, le compilateur TypeScript jette une erreur pendant la compilation puisque cette propriété n’existe pas dans l’interface Student
.
De même, 100 — _student.firstName
n’est pas une opération valide puisque la propriété firstName
est un type de string
et la dernière fois que j’ai vérifié, vous ne pouvez pas soustraire un string
d’un number
est JavaScript (résultats dans NaN
).
Dans l’exemple ci-dessus, nous avons utilisé la manière traditionnelle d’écrire le type de fonction pour le champ getSalary
. Cependant, vous pouvez également utiliser la syntaxe de fonction sans le corps pour la même chose, ce qui est généralement utilisé dans les interfaces.
interface Student {
firstName: string;
lastName: string;
age: number;
getSalary(base: number): number;
};
Propriétés optionnelles
Parfois, vous avez besoin qu’un objet ait une propriété qui détient des données d’un type particulier, mais il n’est pas obligatoire d’avoir cette propriété sur l’objet. Ceci est similaire aux paramètres optionnels des fonctions que nous avons appris dans la leçon précédente.
Ces propriétés sont appelées propriétés optionnelles. Une interface peut contenir des propriétés optionnelles et nous utilisons l’annotation ?:Type
pour les représenter, tout comme les paramètres de fonction optionnels.
Dans l’exemple ci-dessus, l’interface Student
possède la propriété age
qui est optionnelle. Cependant, si la propriété age
est fournie, elle doit avoir une valeur du type number
.
Dans le cas de l’objet ross
qui est un type de l’interface Student
, nous n’avons pas fourni la valeur de la propriété age
qui est légale, cependant, dans le cas de monica
, nous avons fourni la propriété age
mais sa valeur est string
qui n’est pas légale. Par conséquent, le compilateur TypeScript jette une erreur.
L’erreur peut sembler bizarre mais elle est en fait logique. Si la propriété age
n’existe pas sur un objet, le object.age
renverra undefined
qui est un type de undefined
. Si elle existe, alors la valeur doit être du type number
.
Hence la valeur de la propriété age
peut être soit du type undefined
, soit du type number
qui, dans TypeScript, est représenté en utilisant la syntaxe d’union number | undefined
.
💡 Nous apprendrons les unions de type dans une leçon sur le système de type.
Cependant, les propriétés optionnelles posent de sérieux problèmes pendant l’exécution du programme. Imaginons que nous utilisions la propriété age
dans une opération arithmétique mais que sa valeur soit undefined
. C’est une sorte de problème sérieux.
Mais la bonne chose est que le compilateur TypeScript ne permet pas d’effectuer des opérations illégales sur une propriété optionnelle puisque sa valeur peut être undefined
.
Dans l’exemple ci-dessus, nous effectuons une opération arithmétique sur la propriété age
qui est illégale car la valeur de cette propriété peut être number
ou undefined
dans le runtime. Effectuer des opérations arithmétiques sur undefined
donne NaN
(pas un nombre).
💡 Cependant, pour le programme ci-dessus, nous devions définir le drapeau
--strictNullChecks
àfalse
qui est un drapeau de compilateur TypeScript. Si nous fournissons cette option, le programme ci-dessus se compile très bien.
Pour éviter cette erreur ou cet avertissement, nous devons dire explicitement au compilateur TypeScript que cette propriété est un type de number
et non le number
ou undefined
. Pour cela, nous utilisons l’assertion de type (AKA conversion de type ou typecasting).
Dans le programme ci-dessus, nous avons utilisé (_student.age as number)
qui convertit le type de _student.age
de number | undefined
en number
. C’est une façon de dire au compilateur TypeScript, « Hé, ceci est un nombre ». Mais une meilleure façon de gérer cela serait de vérifier également si _student.age
est undefined
au moment de l’exécution, puis d’effectuer l’opération arithmétique.
💡 Nous apprendrons les assertions de type dans une leçon sur le système de type.
Type de fonction utilisant une interface
Non seulement la forme d’un objet ordinaire, mais une interface peut également décrire la signature d’une fonction. Dans la leçon précédente, nous avons utilisé l’alias de type pour décrire un type de fonction, mais les interfaces peuvent également le faire.
interface InterfaceName {
(param: Type): Type;
}
La syntaxe pour déclarer une interface comme type de fonction est similaire à la signature de la fonction elle-même. Comme vous pouvez le voir dans l’exemple ci-dessus, le corps de l’interface contient la signature exacte d’une fonction anonyme, sans le corps bien sûr. Ici, les noms des paramètres n’ont pas d’importance.
Dans l’exemple ci-dessus, nous avons défini IsSumOdd
l’interface qui définit un type de fonction qui accepte deux arguments de type number
et renvoie une valeur boolean
. Maintenant, vous pouvez utiliser ce type pour décrire une fonction car le type d’interface IsSumOdd
est équivalent au type de fonction (x: number, y: number) => boolean
.
Une interface avec une signature de méthode anonyme décrit une fonction. Mais une fonction dans le royaume JavaScript est également un objet, ce qui signifie que vous pouvez ajouter des propriétés à une valeur de fonction tout comme un objet. Il est donc parfaitement légal que vous puissiez définir n’importe quelles propriétés sur une interface de type fonction.
Dans l’exemple ci-dessus, nous avons défini une interface LapTimes
qui peut contenir des noms de propriétés de type number
et des valeurs de type number
. Cette interface peut représenter une structure de données qui peut être indexée en utilisant des clés number
donc le tableau ross
et les objets monica
et joey
sont légaux.
Cependant, l’objet rachel
ne respecte pas la forme de LapTimes
puisque la clé one
est un string
et qu’on ne peut y accéder qu’en utilisant des string
comme rachel
et rien d’autre. Par conséquent, le compilateur TypeScript lancera une erreur comme indiqué ci-dessus.
Il est possible d’avoir certaines propriétés obligatoires et certaines facultatives dans un type d’interface indexable. Cela peut être très utile lorsque nous avons besoin qu’un objet ait une certaine forme, mais cela n’a pas vraiment d’importance si nous obtenons des propriétés supplémentaires et non désirées dans l’objet.
Dans l’exemple ci-dessus, nous avons défini une interface LapTimes
qui doit contenir la propriété name
avec la valeur string
et la propriété optionnelle age
avec la valeur number
. Un objet de type LapTimes
peut aussi avoir des propriétés arbitraires dont les clés doivent être number
et dont les valeurs doivent aussi être number
.
L’objet ross
est un objet LapTimes
valide même s’il n’a pas la propriété age
puisqu’elle est optionnelle. Cependant, monica
possède la propriété age
mais sa valeur est string
donc il n’est pas conforme à l’interface LapTimes
.
L’objet joey
n’est pas non plus conforme à l’interface LapTimes
car il possède une propriété gender
qui est un type de string
. L’objet rachel
n’a pas la propriété name
qui est requise dans l’interface LapTimes
.
💡 Il y a quelques gotchas auxquels nous devons faire attention en utilisant les types indexables. Ils sont mentionnés dans cette documentation.
Extending Interface
Comme les classes, une interface peut hériter des propriétés d’autres interfaces. Cependant, contrairement aux classes en JavaScript, une interface peut hériter de plusieurs interfaces. Nous utilisons le mot clé extends
pour hériter d’une interface.
En étendant une interface, l’interface enfant obtient toutes les propriétés de l’interface parent. Ceci est très utile lorsque plusieurs interfaces ont une structure commune et que nous voulons éviter la duplication de code en prenant les propriétés communes dans une interface commune qui peut être héritée plus tard.
Dans l’exemple ci-dessus, nous avons créé une interface Student
qui hérite des propriétés de l’interface Person
et Player
.
Déclarations d’interfaces multiples
Dans la section précédente, nous avons appris comment une interface peut hériter des propriétés d’une autre interface. Ceci a été fait en utilisant le mot-clé extend
. Cependant, lorsque des interfaces portant le même nom sont déclarées dans le même module (fichier), TypeScript fusionne leurs propriétés ensemble tant qu’elles ont des noms de propriétés distincts ou que leurs types de propriétés conflictuels sont les mêmes.
Dans l’exemple ci-dessus, nous avons déclaré l’interface Person
plusieurs fois. On obtiendra une seule déclaration d’interface Person
en fusionnant les propriétés de toutes les déclarations d’interface Person
.
💡 Dans la leçon Classes, nous avons appris qu’une
class
déclare implicitement une interface et qu’une interface peut étendre cette interface. Ainsi, si un programme a une classePerson
et une interfacePerson
, alors le typePerson
final (interface) aura des propriétés fusionnées entre la classe et l’interface.
Interfaces imbriquées
Une interface peut avoir des structures profondément imbriquées. Dans l’exemple ci-dessous, le champ info
de l’interface Student
définit la forme d’un objet avec des propriétés firstName
et lastName
.
De même, il est parfaitement légal pour un champ d’une interface d’avoir le type d’une autre interface. Dans l’exemple suivant, le champ info
de l’interface Student
a le type de l’interface Person
.
.