heymath < Blog />

Le cast de type en TypeScript

Publié le

Hey!

J’ai envie de parler d’un sujet assez simple dans le principe, mais pas toujours facile à aborder ou à correctement utiliser : le cast de type en TypeScript.

Comment cast ?

Il existe 2 syntaxes pour cast un type en TypeScript, les deux font exactement la même chose :

// chevrons
const article = <HTMLElement> document.querySelector('.article');

// as
const article = document.querySelector('.article') as HTMLElement;

Que choisir ?

Les développeurs à qui j’ai eu l’occasion de poser la question, m’ont tous donné la même réponse :

Ça dépend

Non, je déconne, pour une fois la réponse est unanime :

const article = document.querySelector('.article') as HTMLElement;

Pourquoi “as” ?

React

Là aussi tout le monde me répond à l’unison :

Le cast avec les chevrons n’est pas compatible avec application React à cause de la syntaxe JSX. Avec la syntaxe “as” tu n’auras jamais ce problème.

Lisibilité

Pour ceux qui sont perplexes ou pensent très fort “mais mon projet n’utilisera jamais de React !” : je trouve la syntaxe avec le mot-clé “as” plus parlante, plus claire.

D’une part, la lecture “la variable égale la valeur de ce type“ semble naturelle et colle bien à la syntaxe “as”.
D’autre part, la syntaxe avec les chevrons est plus exotique dans le milieu JavaScript donc plus “bizarre” et moins parlante.

Or nous sommes des développeurs et passons la majorité de notre temps à lire.

Pourquoi cast ?

Tout d’abord, une petite précision. Quand on parle de cast en TypeScript, il s’agit de définir le type de notre variable, ce n’est pas un cast pour changer/forcer le type d’une variable comme en PHP par exemple.

// PHP
$aNumber = 5;
// 5
$aNumberAsString = (string) $aNumber;
// '5'
// TypeScript
const aNumber = 5;
// 5
const aNumberAsString = aNumber as string;
// ERROR: Conversion of type 'number' to type 'String' may be a mistake
// because neither type sufficiently overlaps with the other.

Pourtant un nombre a bien une méthode toString, mais ce n’est pas ce qu’on essaie de faire avec le cast en TypeScript.

const aNumber = 5;
// 5
const aNumberAsString = aNumber.toString();
// '5'

Pourquoi cast alors ? Et bien prenons cet exemple :

const nav = document.querySelector('.nav');
nav.style.backgroundColor = '#fff';
// ERROR: Object is possibly 'null'.

La méthode querySelector retourne un Element ou null dans le cas où aucun élément n’est trouvé. TypeScript jette alors une erreur car au runtime on accèdera potentiellement à la propriété style de null, ce qui n’est pas possible.

Le cast de type devient alors utile lorsqu’on sait que l’élément sera toujours présent dans le DOM pour indiquer qu’il devrait toujours être présent dans le DOM.

const nav = document.querySelector('.nav') as Element;
nav.style.backgroundColor = '#fff';
// ERROR: Property 'style' does not exist on type 'Element'.

Et oui, on obtient toujours une erreur ; on en arrive au deuxième exemple : le cast vers un type enfant.

Dans le cas ci-dessus, le type Element ne possède pas de propriété style, il y a donc une erreur. En revanche, il existe des classes qui étendent Element et qui possèdent cette propriété, comme HTMLElement par exemple. Grâce au cast, on peut définir que l’Element retourné sera plus précisement un HTMLElement :

const nav = document.querySelector('.nav') as HTMLElement;
nav.style.backgroundColor = '#fff';

Maintenant, libre à nous d’utiliser toute l’API de HTMLElement avec la variable nav.

Erreur commune

Une erreur que j’ai souvent pu voir, c’est le mélange des deux cas ci-dessus qui résulte en un code qui perd de son sens, qui devient même incohérent :

const nav = document.querySelector('.nav') as HTMLElement;

if (nav === null) {
  return;
}

nav.style.backgroundColor = '#fff';

Voici ce que nous dit le code :

  • nav est toujours de type HTMLElement
  • nav est peut-être null

Ouais, c’est pas faux

Ouais, c’est pas faux ! — Perceval (Kaamelott)

Voici ce que le développeur souhaitait nous dire :

  • nav n’est pas toujours dans le DOM, donc peut-être null
  • nav est forcément un type HTMLElement s’il est dans le DOM

Alors comment faire ? Et bien ma réponse ne vient pas de TypeScript, mais de JavaScript lui-même :

const nav = document.querySelector('.nav');

if (!(nav instanceof HTMLElement)) {
  return;
}

nav.style.backgroundColor = '#ffffff';

Il y a sûrement plein d’autres manières de faire, mais je trouve celle-là simple :
si nav est bien un HTMLElement alors /* ... */

Conclusion

Le cast n’est donc pas un sujet très compliqué dans l’absolu, mais lorsqu’on souhaite faire taire à tout prix TypeScript qui nous crie “erreur, essaye encore”, on peut vite prendre de mauvais raccourcis et de mauvaises habitudes. Mieux vaut se poser, lire ce qu’on a écrit et voir si cela exprime bien notre réflexion ou notre intention.


Mathieu Dutto / développeur frontend chez Evaneos. Je m’intéresse aux performances web et à la maintenabilité du code. Vous pouvez me retrouver sur Twitter et GitHub.

Mathieu Dutto © 2020, built with Gatsby