2. Refactoriser, comprendre

Adrien Foucart

Retour à l’index

Précédent: 1. Point de départ

Au chapitre précédent, j’ai fait un “Hello World” Typescript en dessinant un rectangle dans un <canvas> HTML. En chemin, j’ai laissé quelques questions en suspend. On va donc chercher à y répondre aujourd’hui… et voir quelles autres questions se soulèvent au passage.

Reprenons ce que je voulais faire:

Un point supplémentaire auquel j’ai pensé depuis est que j’aimerais bien commencer aussi à organiser mon code en différents fichiers, ce qui me permettra de chercher à comprendre comment fonctionnent les imports en Typescript.

Refactorisation

La première partie ne nous apporte pas grand chose de nouveau, si ce n’est une petite séance de rappel sur comment fonctionne le forEach et les fonctions anonymes en Javascript.

interface Drawable {
    draw(ctx: CanvasRenderingContext2D): void;
}
class Rectangle {

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

    public draw(ctx: CanvasRenderingContext2D): void {
        ctx.rect(this.x, this.y, this.w, this.h);
        ctx.stroke();
    }
}
class DrawingBoard {
    private drawings: Drawable[];

    constructor(private ctx: CanvasRenderingContext2D) {
        this.drawings = [];
    }

    public add(drawing: Drawable){
        this.drawings.push(drawing);
    }

    public draw(): void {
        this.drawings.forEach((e) => {
            e.draw(this.ctx);
        })
    }
}
const canvas: HTMLCanvasElement = document.getElementById("drawing") as HTMLCanvasElement;
const board = new DrawingBoard(canvas.getContext("2d")!);
board.add(new Rectangle(100, 150, 200, 50));
board.add(new Rectangle(150, 160, 15, 70));
board.draw();

tsc drawing.ts pour compiler, et puis on peut tester dans drawing.html, dans lequel on n’a rien changé.

!?

Le Javascript a pas mal changé depuis la dernière fois que j’en ai fait “sérieusement”. Et encore, je n’ai jamais vraiment plongé “à fond” dans le langage. Une des difficultés ici en me lançant dans cette aventure Typescript, c’est de bien distinguer ce que je dois comprendre dans les particularités de la surcouche Typescript, et ce qui est juste une propriété de base du Javascript. Sinon, je passe beaucoup de temps à chercher dans le documentation des choses qui ne s’y trouvent pas.

L’utilisation des ! et des ? semble faire partie des choses où il est parfois un peu compliqué de démêler ce qui vient d’où. Essayons.

Ce dernier usage du ! fait partie plus généralement des “types assertions”, comme le as qu’on utilise aussi dans notre programme. Ce as n’est pas un “casting” en tant que tel, mais bien une déclaration au compilateur que vous êtes tout à fait confiant dans le fait que votre variable aura ce type là. On peut faire cette déclaration soit dans le sens “plus spécifique”, comme on l’a fait (HTMLElement vers HTMLCanvasElement), soit dans l’autre sens. Je ne vois pas trop l’intérêt de cette dernière fonctionnalité, d’ailleurs. Elle permet d’ailleurs en pratique, comme le note la documentation, de faire toute déclaration que l’on veut en combinant deux as avec le type any au milieu. Par exemple:

const a = 1;
const b: string = a as any as string;
console.log(b);

Va être parfaitement accepté par le compilateur, et va bien afficher 1 dans la console. Mais bon, si c’est pour faire ça, on peut juste faire du Javascript directement…

Le ? mène aussi une double vie entre le Javascript et le Typescript:

Reste donc la question de si c’est une bonne idée dans mon programme de faire:

const canvas: HTMLCanvasElement = document.getElementById("drawing") as HTMLCanvasElement;
const board = new DrawingBoard(canvas.getContext("2d")!);

Qu’est-ce qui peut mal tourner ici ?

getContext ne renverra null que si le “context identifier” (ici "2d") n’est pas supporté, ce qui est fort peu probable ("2d" est la valeur par défaut), ou si le <canvas> a déjà été initialisé avec un autre contexte. Je ne vois pas vraiment de risque que ça arrive, donc je vais pour l’instant le laisser comme ça. La syntaxe de la gestion des exceptions me semble suffisamment normale pour que je ne m’en préoccupe pas tant que je n’en ai pas vraiment besoin.

Par contre, si je fais des modifications dans le HTML, je pourrais tout à fait me retrouver à un moment à changer l’identifiant du canvas sans m’en rendre compte. Si je le fais – ça se teste facilement – mes rectangle disparaissent, et j’ai dans la console une erreur: “Uncaught TypeError: can’t access property”getContext”, canvas is null”. Une option serait, dans ce cas, de créer directement le canvas depuis notre code.

function createCanvas(): HTMLCanvasElement {
    const canvas = <HTMLCanvasElement>document.createElement("canvas");
    canvas.width = 500;
    canvas.height = 500;
    document.body.appendChild(canvas);
    return canvas;
}

const canvas: HTMLCanvasElement = document.getElementById("drawing") as HTMLCanvasElement ?? createCanvas();
const board = new DrawingBoard(canvas.getContext("2d")!);
board.add(new Rectangle(100, 150, 200, 50));
board.add(new Rectangle(150, 160, 15, 70));
board.draw();

Même si c’est sympa comme démonstration des fonctionnalités de ?? et as, c’est du coup un peu se compliquer la vie. Ce n’est par contre sans doute pas une mauvaise idée de retirer le <canvas> du HTML et de laisser la responsabilité au fichier Javascript de le créer, sans avoir plus besoin de ?? et de as:

function createCanvas(w: number = 500, h: number = 500): HTMLCanvasElement {
    const canvas = <HTMLCanvasElement>document.createElement("canvas");
    canvas.width = w;
    canvas.height = h;
    document.body.appendChild(canvas);
    return canvas;
}

const canvas = createCanvas();
const board = new DrawingBoard(canvas.getContext("2d")!);
board.add(new Rectangle(100, 150, 200, 50));
board.add(new Rectangle(150, 160, 15, 70));
board.draw();

Et le <body> du fichier HTML devient minimal:

<body>
    <script src="drawing.js"></script>
</body>

Le résultat du programme est identique. Je ne sais pas trop si je préfère cette version, où celle avec le <canvas> dans le HTML. Je peux voir une certaine logique aux deux. Je vais garder cette version-ci pour l’instant, mais je changerai peut-être d’avis plus tard, une fois que j’aurai décidé un peu plus clairement quel “projet” je développerai pour continuer à apprendre !

En attendant, assez pour l’instant.

Code:

À suivre…

Ce qu’on a laissé de côté:

Suite: 3. Des modules