Types vs. interfaces
Introduction
En fait, en TypeScript, un type et une interface c'est à peu de choses près la même chose.
La différence principale vient de la façon dont on peut combiner ces deux "types de types":
Une interface peut étendre une autre interface, tandis qu'un type ne peut ni étendre un autre type ni étendre une interface.
Les types peuvent se combiner les uns avec les autres, soit par union, soit par intersection.
Cette distinction de comportement entre types et interface reflète bien les fameux paradigmes de l'extension de fonctionnalité - qu' on oppose souvent - que sont la composition et l' héritage. TypeScript propose des constructions pour chacun des deux clans.
Une interface peut en étendre une autre
Les interfaces en TypeScript sont, je pense, similaires aux interfaces dans tous les langages qui ont des interfaces : elles décrivent la "forme" que peut prendre un objet, une classe, une fonction... mais elle n'en définissent pas l'implémentation.
Une interface peut étendre une autre interface, en voici un exemple :
interface Node {
type: string
}
interface TextNode
extends Node {
type: 'TextNode'
value: string
}
Cela dit, la notion d'extension ou d'implémentation enTypeScript est un peu différente d'autres langages comme le C++ ou le PHP par exemple.
En effet on pourrait dire qu'en TypeScript tout étend à peu près tout, dans la mesure où même si on type les paramètres d'une fonction, on pourra lui passer n'importe quel type jugé compatible par TypeScript.
type Rectangle = {
width: number
height: number
}
type Square = {
width: number
height: number
}
const computeArea = (rect: Rectangle) =>
rect.width * rect.height;
const square1: Square = {
width: 10,
height: 10,
};
console.log(computeArea(square1));
En PHP si vous déclarez que votre fonction a besoin d'un paramètre implémentant telle interface, vous ne pourrez utiliser cette fonction qu'avec un paramètre implémentant explicitement l'interface attendue.
En TypeScript, en gros, tant que l'analyse statique du code conclut que ça va marcher, vous faites ce que vous voulez.
Union et intersection de types
Les types en TypeScript peuvent se combiner par union avec l'opérateur "|", ou par intersection avec l'opérateur "&", pour créer des types plus complexes.
type Person = {
name: string
}
type Traveller = Person & {
hasPassport: true
}
type Robot = {
CPUs: number
RAM: number
version: string
}
type Employee = Person | Robot
Quand on fait une intersection de deux types, on déclare que le type résultant aura chacune des propriétés des deux types, en même temps.
Quand on fait une union de deux types, on déclare que le type résultant aura un sous-ensemble de l'union des propriétés des deux types.Mais pas n'importe quel sous-ensemble, un objet, pour appartenir à une union, doit avoir au moins toutes les propriétés de l'un des types de l'union. On verra ça dans l'exemple d'après.
interface Rectangle {
width: number
height: number
}
const computeArea = (rect: Rectangle) =>
rect.width * rect.height;
type hasWidth = {
width: number
}
type hasHeight = {
height: number
}
type UnionSquare = hasWidth | hasHeight;
const unionSquareW: UnionSquare = {
width: 15,
};
const unionSquareH: UnionSquare = {
height: 15,
};
const unionSquare: UnionSquare = {
width: 15,
height: 32,
};
/**
* La ligne qui suit provoque l'erreur suivante :
*
* Argument of type 'UnionSquare' is not assignable to
* parameter of type 'Rectangle'. Property 'height'
* is missing in type 'hasWidth' but
* required in type 'Rectangle'.
*/
console.log(
computeArea(unionSquare),
);
type InterSquare = hasWidth & hasHeight
const interSquare: InterSquare = {
width: 30,
height: 30,
};
/**
* Mais ça, ça va très bien :
*/
console.log(
computeArea(interSquare),
);
Comme je le disais plus haut: pour être mêmbre d'une union de types A et B, on objet peut avoir n'importe laquelle des propriétés de A ou de B, mais il doit au moins avoir soit toutes les propriétés de A, soit toutes les propriétés de B.
type hasWidth = {
width: number
}
type hasHeight = {
height: number
unitOfMeasurement: string
}
type UnionSquare = hasWidth | hasHeight;
const unionSquareW: UnionSquare = {
width: 15,
};
/**
* La ligne qui suit provoque l'erreur suivante :
*
* Type '{ height: number; }' is not assignable to type
* 'UnionSquare'. Property 'unitOfMeasurement'
* is missing in type '{ height: number; }'
* but required in type 'hasHeight'.
*/
const unionSquareH: UnionSquare = {
height: 15,
};
Enfin, ça va de soi, mais un membre d'une union de types ne peut pas avoir de propriétés qui ne sont dans aucun des types de l'union.
type hasWidth = {
width: number
}
type hasHeight = {
height: number
}
type UnionSquare = hasWidth | hasHeight;
/**
* La ligne qui suit provoque l'erreur suivante :
*
* Type '{ width: number; somethingElse: boolean; }'
* is not assignable to type 'UnionSquare'.
* Object literal may only specify known properties,
* and 'somethingElse'
* does not exist in type 'UnionSquare'.
*/
const union: UnionSquare = {
width: 15,
somethingElse: true;
};
fm.de.jouvencel@gmail.com