Niveau Niveau confirmé

Découverte de TypeScript

Tutorieljavascript

Publié par le (14215 lectures)

javascript js vue ts typescript

Si vous êtes ici, c'est que vous avez sûrement déjà entendu parler de TypeScript. Au premier abord, on peut ne pas vraiment savoir à quoi ça sert, voire même ne pas savoir ce que c'est du tout.

Dans ce tuto, nous allons partir à sa découverte et explorer ce qu'il a à proposer.

Mais c'est quoi TypeScript ?

En prenant la définition depuis la documentation officielle, TypeScript (que nous nommerons "TS" par la suite) est un langage de programmation construit par-dessus Javascript et offrant de nouvelles fonctionnalités.

Il a été créé par Anders Hejlsberg pour Microsoft (rangez vos fourches, le projet est open source) en 2012, dans l'objectif de combler les manques de Javascript (JS) comme par exemple les classes qui, à l'époque n'existaient pas encore.

Anders Hejlsberg
Anders Hejlsberg

Ce type de technologie est souvent appelé "Superset", un exemple très connu est SASS pour le CSS.

Concrètement, nous allons pouvoir écrire du code TypeScript qui sera ensuite compilé en Javascript natif.

Et donc finalement, ça sert à quoi ?

L'une des principales fonctionnalités de TypeScript se trouve dans son nom, "Type".

En effet, grâce à lui, il devient possible de typer son code, c'est à dire de indiquer des types explicites à ses variables, fonctions, etc. qui devront être respectés.

wtf

Si cela n'est pas tout de suite clair, voici un exemple.

En JS, nous pouvons écrire une fonction prenant un nombre en paramètre et qui retourne son double:

function get_double(my_number) {
  return my_number * 2
}

/* Aucune erreur, ça fait 4 */
const result = get_double(2)
/* Aucune erreur même si l'on ne passe pas un nombre, on obtient également 4 */
const second_result = get_double('2')
/* Aucune erreur, cependant on obtient NaN */
const third_result = get_double('foo')
/* Aucune erreur, cependant on obtient NaN */
const third_result = get_double()

Dans cet exemple, il n'y a rien d'explicite pour dire que my_number doit être un nombre.

  • Si l'on passe bien un nombre, aucun souci, c'est ce qui est voulu.

  • Si l'on passe une string contenant un nombre, JS va le comprendre et utiliser notre paramètre comme un nombre.

  • Si l'on passe une string classique, apparemment aucun souci mais "foo" * 2 n'est pas une opération valide et get_double ne retournera pas un nombre mais NaN.

  • Si l'on ne passe rien, apparemment aucun souci mais undefined * 2 n'est pas une opération valide et get_double ne retournera pas un nombre mais NaN.

Vous avez sûrement compris que ce comportement n'est pas idéal pour développer et il serait préférable de ne pas envoyer notre code en production tout de suite...

Avec TS, il nous est possible de définir le type de my_number pour être sûr de ne pouvoir passer que des nombres.

function get_double(my_number: number) {
  return my_number * 2
}

const my_result = get_double(2)

// Argument of type 'string' is not assignable to parameter of type 'number'
const my_second_result = get_double('2')
// Argument of type 'string' is not assignable to parameter of type 'number'
const my_third_result = get_double('foo')

Notre code TypeScript n'est pas vraiment différent de notre code JS (pour l'instant en tout cas...). On peut voir qu'il a suffi de remplacer get_double(my_number) par get_double(my_number: number).

Il nous sera ensuite impossible de compiler si nous passons autre chose qu'un nombre à get_double.

🎊 Vous savez maintenant quelle est l'utilité principale de TypeScript, mais vous allez voir que ce n'est pas tout !

Autocomplétion

Une des plus grandes forces de TS est en réalité de pouvoir laisser notre éditeur mieux comprendre notre code. En effet, en typant explicitement notre code, notre éditeur est capable de nous proposer des choses qui ne seraient normalement pas possibles.

Reprenons l'exemple de get_double.

JS

function get_double(my_number) {
  my_number.split() // Mon éditeur ne comprend pas que `split` n'existe pas sur un nombre
  return my_number * 2
}

TS

function get_double(my_number: number) {
  my_number.split() // Property 'split' does not exist on type 'number', c'est la catastrophe.
  return my_number * 2
}

Cette aide supplémentaire va nous faire gagner un temps précieux et surtout nous éviter des erreurs bêtes mais courantes.

L'autocomplétion possible avec TS va bien au-delà de cet exemple, mais c'est en dehors du périmètre de cette découverte 😉.

C'est bien beau, mais comment on compile du TypeScript ?

La méthode la plus simple et rapide pour tester est d'installer le module npm typescript et d'utiliser la commande tsc.

npm install -g typescript
tsc ./mon-fichier.ts

Cette dernière nous sortira un fichier mon-fichier.js sans aucune source TS encore présente.

Avec les outils comme Webpack, Rollup ou encore un que vous connaissez sûrement Gulp, il est plus facile de compiler son code.

La compilation se fera en tâche de fond et vous n'aurez pas à vous en occuper une fois configurée.

Certains frameworks front-end comme Vue.js (notre favori chez Alsacréations) proposent même d'installer TypeScript à l'aide d'une simple commande.

vue add typescript

Si vous utilisez un autre framework, Vite.js propose des modèles prêts à l'emploi pour React, Svelte, Vue, et même du JS natif !

Il y a encore d'autres méthodes pour utiliser TypeScript mais cela est en dehors du périmètre de ce tutoriel.

Pour suivre le reste de cet article, vous pouvez tout simplement utiliser le Playground officiel.

Les bases de TS

Maintenant que nous savons à quoi sert TypeScript et comment l'installer sur notre projet, comment écrire du code TS ?

Typer des variables simples

const my_string = 'Hello World!' // Valide en JS et TS
const my_typed_string: string = 'Hello World!' // Valide en TS

Ici nous avons deux solutions, nous pouvons donner le type explicitement ou laisser notre éditeur le comprendre tout seul.

En effet, my_string étant défini en brut, notre éditeur est capable d'inférer son type tout seul. Avec my_typed_string nous ne faisons que le dire explicitement.

Pour typer une variable explicitement, il suffit d'ajouter : {type} après le nom de la variable. (Remplacez "{type}" par le type voulu).

Les types les plus courants sont: string, number, boolean et void.

Le type void est un type particulier, en effet il permet de dire rien. Cela est particulièrement utile dans les fonctions qui ne retournent rien, en réalité le retour sera undefined.

Définition de tableaux

Pour travailler avec des tableaux, deux possibilités s'offrent à nous.

  • Ajouter [] après le type (: {type}[]), [] étant la syntaxe commune d'un tableau.
  • Utiliser un Generic que nous verrons un peu plus tard.
const my_string_array: string[] = ['Hello', 'World!']
const my_string_array: Array<string> = ['Hello', 'World!']

/* Argument of type 'number' is not assignable to parameter of type 'string' */
my_string_array.push(1337)

Définition d'objets

Si vous connaissez déjà la syntaxe des objets JS, bonne nouvelle, vous connaissez également celle de TS :p

const my_object: { message: string, words: number } = { message: 'Hello World!', words: 2 }

my_object.message = 1337 // Type 'number' is not assignable to type 'string'

Les fonctions

Les fonctions peuvent elles aussi être typées au niveau de leurs paramètres ainsi que de leur valeur de retour.

const dogs: Dog[] = [/* ... */]

/**
 * Ici, on dit que `name` est une string,
 * et que la fonction retourne un `Dog` ou `undefined`
 */
function get_dog(name: string): Dog | undefined {
  return dogs.find(dog => dog.name === name)
}

/**
 * Fonction qui ne retourne rien avec le type `void`
 */
function pet_dog(): void {
  /* happy dog */
}

/**
 * Fonction fléchée
 */
const get_dog = (name: string): Dog | undefined => dogs.find(dog => dog.name === name)

const my_dog = get_dog('Idefix') // Dog | undefined

Création et réutilisation de types

L'autre grande force de TS réside dans la possibilité de créer ses propres types et de les réutiliser comme bon nous semble.

Dans la suite de l'article, nous utiliserons Dog comme exemple. Malheureusement nos Dog seront un peu moins mignons que celui-ci...

type Dog = {
  name: string,
  age: number,
  cute: boolean
}

const cute_dog: Dog = { name: 'Idefix', age: 2, cute: true }
/**
 * Property 'name' is missing in type '{ age: number; cute: false; }' but required in type 'Dog'
 *
 * PS: C'est juste pour l'exemple, tous les chiens sont mignons 😉
 */
const ugly_dog: Dog = { age: 2, cute: false }

Un type peut également être défini (et c'est souvent fait de cette façon) avec une Interface

interface Dog {
  name: string
  age: number
  cute: boolean
}

const cute_dog: Dog = { name: 'Idefix', age: 2, cute: true }

/* Property 'name' is missing in type '{ age: number; cute: false; }' but required in type 'Dog' */
const ugly_dog: Dog = { age: 2, cute: false }

Les interfaces sont souvent utilisées pour décrire une class en JS

/*
 * Class 'Doggo' incorrectly implements interface 'Dog'.
 * Property 'cute' is missing in type 'Doggo' but required in type 'Dog'
 */
class Doggo implements Dog {
  name = 'Idefix'
  age = 2
}

Définition de fonctions dans une interface

interface Dog {
  give_food: () => void
  give_bath: (use_shampoo: boolean) => void
  get_name: () => string
}

Types dynamiques avec les Generics

Enfin, il est possible de créer des types dépendants d'autres en leur passant ce qu'on pourrait appeler des paramètres par abus de language. C'est cela que l'on appelle cela des Generics.

/**
 * Notre Generic ici est T, il permet d'y passer un paramètre comme dans une fonction JS
 */
interface Dog<T> {
  name: string
  age: T
  cute: boolean
}

/* OK 🐶 */
const cute_dog: Dog<number> = { name: 'Idefix', age: 2, cute: true }
/* Type 'string' is not assignable to type 'number' */
const second_cute_dog: Dog<number> = { name: 'Idefix', age: '2', cute: true }
/* OK 🐕‍🦺 */
const third_cute_dog: Dog<string> = { name: 'Idefix', age: '2', cute: true }
/**
 * On peut également passer plusieurs Generics,
 * De plus, les interfaces ne sont pas les seules à pouvoir les utiliser
 */
type Dog<N, A, C> = {
  name: N,
  age: A,
  cute: C
}

/**
 * Les fonctions peuvent aussi le faire
 */
function get_dog<N, A, C>(name: N): Dog<N, A, C>

const my_dog = get_dog<string, number, boolean>('Idefix') // Dog<string, number, boolean>
const my_dog = get_dog<string, string, boolean>('Idefix') // Dog<string, string, boolean>

Les Generics dans les fonctions sont très puissants, ils permettent une autocomplétion plus avancée:

type Dog<N> = {
  name: N,
  age: number,
  cute: boolean
}

function get_dog<N>(name: N): Dog<N> {
  return { /* */ }
}

/**
 * Je ne suis pas obligé de passer mon Generic explicitement,
 * mon éditeur comprend tout seul que j'ai passé
 * une string pour `name`, et comprend que N est maintenant une string.
 */
const my_dog = get_dog('Idefix') // Dog<string>

Types utilitaires et operators

Par défaut, TS possède des types utilitaires pour nous aider à en construire de nouveaux, comme par exemple ReturnType.

Nous avons également accès à des mots clés comme keyof et typeof pour nous aider à inférer de nouveaux types.

Par exemple typeof permet d'obtenir le type d'une variable JS.

const message = 'Hello World!'

type MessageType = typeof message // string

Deuxième exemple, ReturnType<T> permet d'obtenir le type de retour d'une fonction.

function get_dog(name: string): Dog {
  return { /* */ }
}

type GetDogType = typeof get_dog // (name: string) => Dog

/**
 * Ici ReturnType utilise un Generic, on lui passe `GetDogType`
 */
type GetDogReturnType = ReturnType<GetDogType> // Dog

J'ai quand même l'impression que c'est plus compliqué qu'autre chose...

En effet, à première vue TS n'est pas si simple à prendre en main. En débutant, vous obtiendrez peut-être de nombreuses erreurs alors que votre code fonctionnait en JavaScript.

Cependant avec un peu de patience et un temps d'adaptation pour bien comprendre ces erreurs, votre code en sortira plus stable et moins sujet aux bugs invisibles remontés par Javascript lors d'une mise en production... (c'est du vécu 😄)

Il n'est pas nécessaire d'utiliser TS à 100% dès le départ, il est tout à fait possible de migrer doucement une base de code JS vers TS notamment avec allowJs ou même de n'utiliser les types qu'avec JSDoc.

Mot de la fin

Nous avons vu ensemble ce qu'est TypeScript et pourquoi c'est l'une des technologies front-end les plus populaires.

Et vous, l'avez-vous essayé ? Qu'en pensez-vous ?

👋

Commentaires

J'avoue que je n'étais pas très chaud quand on y est passés chez Proton, mais au final, c'est pas désagréable : moins de surprises, plus d'explicite et ça aide même pour la documentation. Un peu rude au début, mais on s'y fait.

Bonjour,

je reviens sur le terme "compiler", ne faut-il pas lui préférer :

a. compiler de source à source

b. transpiler

c. transcompiler

d. préprocéder

Oui, c'est pas simple ! :-)

@Francois Parmentier : Oui c'est parfois suffisant. Je dirais même que ça vaut le coup, rien que pour l'autocomplétion (en plus de pouvoir commenter son code encore plus clairement) ! :p

Commenter

Vous devez être inscrit et identifié pour utiliser cette fonction.

Connectez-vous (déjà inscrit)

Oubli de mot de passe ? Pas de panique, on va le retrouver

Pas encore inscrit ? C'est très simple et gratuit.