1. Point de départ

Adrien Foucart

Retour à l’index

Pour commencer à s’aventurer dans l’univers du Typescript, il nous faut un plan. Commençons par rassembler quelques sources:

Ma stratégie pour commencer est de partir du site officiel. C’est ce qui a le plus de chances d’être à jour et complet. Puis, s’il y a des choses qui nécessitent un regard différent, j’irai picorer dans les autres tutoriels – et sur StackOverflow et autres sources d’explications plus ou moins fiables, évidemment. J’éviterai juste ChatGPT, Copilot ou autres. Je préfère que mes informations, bonnes ou mauvaises, soient d’origine humanoïde.

Hello, world?

Typescript est une surcouche de Javascript, qui nécessite un compilateur, lequel peut être obtenu par Node.js. On va donc commencer par se procurer Node.js. Après installation (j’ai préféré le “prebuilt” au Docker pour l’instant, ne multiplions pas les sources de confusion) de Node.js, je peux maintenant installer TypeScript avec:

npm install -g typescript

Jusqu’ici, tout va bien.

Je n’aime pas juste recopier l’exemple donné. Pour apprendre, il faut un peu sortir du sentier, ça augmente les chances de se planter, et donc de mieux cerner le fonctionnement réel de l’engin. L’exemple part d’un fichier greeter.ts pour définir une classe, une fonction et une interface, et manipuler le DOM de greeter.html pour y ajouter un “Hello, …”. Essayons quelque chose d’un peu différent: utilisons le <canvas> HTML pour afficher un rectangle dans la fenêtre. On va donc:

<script>
const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");

ctx.rect(10,10, 150,100);
ctx.stroke();
</script> 

Fichier Typescript

Commençons par l’interface:

interface Drawable {
    draw();
}

Mon IDE n’est déjà pas content: il faut que je donne un type de retour à draw(). L’exemple de la documentation n’avait que des attributs dans l’interface, pas des méthodes. Il semble que le type à utiliser est void, essayons.

Je veux que la classe Rectangle ait un contexte “canvas”, une position x, une position y, une largeur et une longueur. Quel est le type d’un “contexte canvas”? D’après un certain Yilmaz sur StackOverflow, c’est CanvasRenderingContext2D.

L’exemple de la documentation semble montrer qu’on peut directement définir les attributs dans le constructeur, pourquoi pas. On récupère ensuite le script pour dessiner le rectangle.

class Rectangle {

    constructor(private ctx: CanvasRenderingContext2D,
                private x: number,
                private y: number,
                private w: number,
                private h: number
    ){}

    public draw(): void {
        this.ctx.rect(this.x, this.y, this.w, this.h);
        this.ctx.stroke();
    }
}

Reste à récupérer le canvas et son contexte, puis à créer et dessiner le rectangle. On va partir du principe que le canvas a un id connu, drawing. Lorsque je le récupère avec document.getElementById("drawing"), je dois donner un type à la variable dans laquelle je le met. Ca devrait être HTMLCanvasElement, mais mon IDE n’est de nouveau pas content si je fais:

const canvas: HTMLCanvasElement = document.getElementById("drawing");

Car getElementById ne renvoie pas nécessairement le bon type. Il peut renvoyer null, ou n’importe quel autre sous-classe de HTMLElement. D’après ce que je comprends ici, je peux soit ajouter un as HTMLCanvasElement pour forcer le getElementById a être du bon type, soit utiliser une non-null assertion avec ! qui indique “je suis sûr que ce n’est pas null, fait moi confiance. Dans les deux cas, je suppose que ça amènera une erreur au runtime si l’élément n’existe pas (ou n’est pas un <canvas>), mais on se préoccupera de ça plus tard.

Le ! ne suffit pas: il enlève le problème du null, mais pas des autres sous-classes. Appliquons donc le “casting”.

const canvas: HTMLCanvasElement = document.getElementById("drawing") as HTMLCanvasElement;
let rect = new Rectangle(canvas.getContext("2d")!, 100, 150, 200, 50);

On utilise cette fois-ci le ! pour getContext, cette fois il n’y a pas d’autre classe possible. Il faudra (plus tard) regarder comment on gère proprement les exceptions, parce que c’est tout de même un peu foireux comme manière de faire…

Bon. Compilons !

tsc drawing.ts

J’ai maintenant un drawing.js qui a été généré.

Fichier HTML

Le fichier HTML est globalement celui de la documentation, mais avec un <canvas> en plus:

<!DOCTYPE html>
<html>
    <head>
        <title>Un dessin</title>
    </head>
    <body>
        <canvas id="drawing" width="500px" height="500px"></canvas>
        <script src="drawing.js"></script>
    </body>
</html>

Est-ce que ça fonctionne ? Non, parce que j’ai oublié de rajouter le rect.draw() à la fin de mon fichier .ts. Corrigeons-le:

interface Drawable {
    draw(): void;
}

class Rectangle {

    constructor(private ctx: CanvasRenderingContext2D,
                private x: number,
                private y: number,
                private w: number,
                private h: number
    ){}

    public draw(): void {
        this.ctx.rect(this.x, this.y, this.w, this.h);
        this.ctx.stroke();
    }
}

const canvas: HTMLCanvasElement = document.getElementById("drawing") as HTMLCanvasElement;
const rect = new Rectangle(canvas.getContext("2d")!, 100, 150, 200, 50);
rect.draw();

On recompile, on re-teste, et…

Ca marche !

Code:

À suivre…

Les questions en suspend pour l’instant: