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 ajouté des propriétés type
et calculate
sur l’interface IsSumOdd
qui décrit une fonction. En utilisant la méthode Object.assign
, nous fusionnons les propriétés type
et calculate
avec une valeur de fonction.
Les interfaces du type fonction peuvent être utiles pour décrire les fonctions constructeurs. Une fonction constructeur est similaire à une classe dont le travail consiste à créer des objets (instances). Jusqu’à ES5, nous n’avions que des fonctions de constructeur pour imiter un class
en JavaScript. Par conséquent, TypeScript compile les classes en fonctions constructeurs si vous ciblez ES5
ou moins.
💡 Si vous voulez en savoir plus sur la fonction constructeur, suivez cet article.
Si nous mettons le mot-clé new
avant la signature de la fonction anonyme dans l’interface, cela rend la fonction constructible. Cela signifie que la fonction ne peut être invoquée qu’en utilisant le mot-clé new
pour générer des objets et non en utilisant un appel de fonction ordinaire. Un exemple de fonction constructrice ressemble à ce qui suit.
function Animal( _name ) {
this.name = _name;
}var dog = new Animal( 'Tommy' );
console.log( dog.name ); // Tommy
Heureusement, nous n’avons pas à travailler avec des fonctions constructrices puisque TypeScript fournit le mot-clé class
pour créer une classe qui est beaucoup plus facile à travailler qu’une fonction constructrice, croyez-moi. En fait, un class
au fond est une fonction constructeur en JavaScript. Essayez l’exemple ci-dessous.
class Animal{
constructor( _name ) {
this.name = _name;
}
}console.log( typeof Animal ); // "function"
Une classe et une fonction constructeur sont une seule et même chose. La seule différence est que la class
nous donne une syntaxe OOP riche pour travailler avec. Par conséquent, une interface de type fonction constructrice représente une classe.

Dans l’exemple ci-dessus, nous avons défini la classe Animal
avec une fonction constructrice qui accepte un argument de type string
. Vous pouvez considérer cela comme une fonction constructeur qui a une signature similaire au constructeur Animal
.
La AnimalInterface
définit une fonction constructeur puisqu’elle a la fonction anonyme préfixée par le mot clé new
. Cela signifie que la classe Animal
se qualifie pour être un type de AnimalInterface
. Ici, le type d’interface AnimalInterface
est équivalent au type de fonction new (sound: string) => any
.
La fonction createAnimal
accepte ctor
argument de type AnimalInterface
, donc nous pouvons passer la classe Animal
comme valeur d’argument. Nous ne pourrons pas ajouter la signature de méthode getSound
de la classe Animal
dans AnimalInterface
et la raison est expliquée dans la leçon Classes.
Types indexables
Un objet indexable est un objet dont les propriétés peuvent être accédées en utilisant une signature d’index comme obj
. C’est la façon par défaut d’accéder à un élément de tableau mais nous pouvons également le faire pour l’objet.
var a = ;
var o = { one: 1, two: 2, three: 3 };console.log( a ); // 1
console.log( a ); // 1 (same as `a.one`)
Parfois, votre objet peut avoir un nombre arbitraire de propriétés sans forme définie. Dans ce cas, vous pouvez simplement utiliser le type object
. Cependant, ce type object
définit toute valeur qui n’est pas number
, string
, boolean
, symbol
, null
, ou undefined
comme discuté dans la leçon de base sur les types.
Si nous devons vérifier strictement si une valeur est un objet JavaScript ordinaire, alors nous pourrions avoir un problème. Cela peut être résolu en utilisant un type d’interface avec une signature d’index pour le nom de la propriété.
interface SimpleObject {
: any;
}
L’interface SimpleObject
définit la forme d’un objet avec string
clés dont les valeurs peuvent être de type de données any
. Ici, le nom de la propriété key
est juste utilisé comme placeholder puisqu’il est entre crochets.

Dans l’exemple ci-dessus, nous avons défini ross
et monica
objet de type SimpleObject
interface. Puisque ces objets contiennent des clés string
et des valeurs de type any
, c’est parfaitement légal.
Si vous êtes confus au sujet de la clé 1
dans le monica
qui est un type de number
, c’est légal puisque les éléments d’objet ou de tableau en JavaScript peuvent être indexés en utilisant des clés number
ou string
, comme indiqué ci-dessous.
var o = { 0: 'Zero', '1': 'One' };
var a = ;console.log( o ); // Zero
console.log( o ); // One
console.log( a ); // One
console.log( a ); // One
Si nous devons être plus précis sur le type de clés et leurs valeurs, nous pouvons sûrement le faire aussi. Par exemple, nous pouvons définir un type d’interface indexable avec des clés de type number
et des valeurs de type number
si nous le voulons.
💡 Un type de clé de signature d’index doit être soit
string
soitnumber
.

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
.

.