Iniciando o uso do TypeScript

Sendo um programador mais voltado para C++, começo meio atrasado a dar uma olhada nessa nova onda de Node.js e TypeScript e tudo mais. Mas depois de ter assistido à uma apresentação sobre TypeScript da Microsoft, fiquei interessado em utilizar, já que Javascript é sofrível quando você começa a criar aplicações mais complexas.

As dificuldades do Javascript ficam ainda mais evidentes agora com todas essas novidades nas versões mais recentes do C++ e até mesmo linguagens novas como Swift. Sei que há a versão ES2015 (ECMAScript 2015), mas ela na prática não pode ser utilizada, por falta de suporte nos navegadores mais utilizados.

Então nada melhor que colocar o TypeScript à prova fazendo uma simples aplicação interessante. 😁

O que faz o TypeScript

TypeScript é uma extensão do antigo Javascript. A extensão mais evidente é utilização de tipos e a verificação, durante a compilação, de erros relacionados a utilização errada de tipos, algo não disponível ao Javascript.

Mas o que mais me interessa nele é o fato dele apenas traduzir código em TypeScript para Javascript comum, de acordo com suas necessidades. Por causa disso, você pode utilizar todas as novas funcionalidades existentes apenas no ES2015 e ainda assim ter seu código funcionando em navegadores antigos que suportam apenas ES3. Isso por si só já é mais do que suficiente para que deixe de usar Javascript diretamente, e começar a criar código novo exclusivamente no TypeScript.

Como ele é um tradutor e verificador de código, temos que instalar ele de forma que seja utilizado pelo editor à sua escolha.

Instalando e Configurando o TypeScript

Se você faz uso do VisualStudio 2013 Update 2 ou superior, o compilador do TypeScript já é instalado por padrão. Caso contrário, temos que instalá-lo para utilizar ele com editor de sua preferência (no meu caso, é o Visual Studio Code).

Como compilador do TypeScript é feito em cima do Node.js, então a primeira coisa é instalar ele.

Depois de instalado o Node.js, é só adicionar o TypeScript pelo console:

npm install -g typescript

Pronto, já podemos compilar código TypeScript para Javascript usando o console:

tsc source.ts

que criará o arquivo source.js!

Configurações do Compilador

Na mesma pasta onde estão seus arquivos de código-fonte, é só criar um arquivo tsconfig.json. No exemplo deste artigo, o arquivo tem o seguinte conteúdo:

{
    "compileOnSave": true,
    "compilerOptions": {
        "target": "es3",
        "module": "commonjs",
        "sourceMap": true,
        "noEmitOnError": true
    }
}

Esta configuração diz para o compilador gerar código Javascript no padrão ES3, utilizando como base um interpretador padrão. A opção sourceMap gera um arquivo .map que pode ser utilizado pelo Firefox para fazer a relação entre o código em TypeScript e o código em Javascript durante uma sessão de depuração. A opção noEmitOnError diz para o compilador não gerar nenhum arquivo caso haja algum erro no nosso código-fonte, evitando assim sobrescrever o arquivo .js que esteja funcionando corretamente.

Caso esteja usando o Visual Studio Code, só precisamos criar uma task que compila nosso arquivo .ts sempre que salvarmos. Abrindo o Command Palette, é só digitar task e escolher Configure Task Runner e depois TypeScript - Watch Mode, para que o editor gere a configuração necessária. Depois disso é só iniciar a task com o comando Run Build Task.

Criando os arquivos necessários

Primeiro é necessário preparar o HTML que vamos utilizar para carregar nosso script e ter o canvas necessário para desenhar. Criei o arquivo canvas.html e fiz apenas o necessário para termos o que precisamos:

<!DOCTYPE hmtl>
<html>
    <head>
        <meta charset="utf8">
        <title>Teste Canvas</title>
    </head>
    <body>
        <canvas id="tela" width="640" height="480"></canvas>
        <script src="canvas.js"></script>
    </body>
</html>

E também criei o arquivo canvas.ts que será o código fonte do nosso pequeno teste.

Criando o Script

Para ter acesso ao canvas e poder desenhar qualquer coisa nele, primeiro é necessário ter o contexto 2D. Além disso, também configuramos algumas constantes para que o restante do programa saiba os limites de nosso canvas.

let tela = document.getElementById("tela") as HTMLCanvasElement;
let ctx = tela.getContext("2d");

const backgroundColor = "#000";
const width = tela.width;
const height = tela.height;

ctx.fillStyle = backgroundColor;
ctx.fillRect(0,0,width,height);

Nas duas primeiras linhas, pegamos o elemento canvas e depois pegamos o contexto 2D para uso posterior. Depois, definimos 3 constantes, que são utilizados pelo restante do script, e finalmente, iniciamos o nosso canvas com um retângulo preenchido de preto, para podermos verificar no navegador que está funcionando.

Um detalhe importante já no começo é que temos que dizer para o TypeScript que o elemento retornado pela chamada document.getElementById é de fato um elemento canvas, daí a necessidade de converter o tipo HTMLElement para HTMLCanvasElement usando a palavra-chave as, já que no tipo HTMLElement não existe o método getContext.

Criando Classes

Nosso pequeno projeto tem como objetivo colocar vários objetos geométricos compostos por variada quantidade de pontos que se movimento pela tela, inspirado na antiga proteção de tela clássica do Windows, Polígonos.

A primeira classe que precisamos é como representar um ponto, composto de duas coordenadas x e y. Além disso, como vamos movimentar o ponto pela tela, também devemos adicionar uma operação para mover o ponto em uma determinada direção repesentada por outro conjunto de coordenadas. Então temos a classe Vector.

class Vector {
    x: number;
    y: number;

    constructor( x:number , y:number) {
        this.x = x;
        this.y = y;
    }

    add(o:Vector) {
        this.x += o.x;
        this.y += o.y;
    }
}

Tendo nossa classe Vector para representar uma coordenada 2D, agora podemos criar a classe que representa um ponto com uma direção, que se move pela tela, se rebatendo pelos cantos. Além das informações de posição e direção, também vamos adicionar um método para fazer esse movimento a cada atualização. Vamos chamá-la de MovingPoint.

class MovingPoint {
    position: Vector;
    direction: Vector;

    constructor(position:Vector,direction:Vector) {
        this.position = position;
        this.direction = direction;
    }

    move() {
        this.position.add(this.direction);
        if (this.position.x <= 0 || this.position.x >= width)
            this.direction.x = -this.direction.x;
        if (this.position.y <= 0 || this.position.y >= height)
            this.direction.y = -this.direction.y;
    }
}

Também precisamos de uma classe que represente um conjunto de pontos formando um polígono... Mas antes, como queremos que cada polígono seja criado aleatoriamente, vamos fazer algumas funções auxiliares: - randomPos cria uma posição aleatória dentro dos limites do nosso canvas. - randomSpeed gera uma velocidade (positiva ou negativa) dentro dos parâmetros. - randomDir cria uma direção com uma velocidade aleatória nos eixos X e Y.

function randomPos(width:number,height:number): Vector {
    let x = Math.random() * width;
    let y = Math.random() * height;
    return new Vector(x,y);
}

function randomSpeed(min:number, max:number) {
    return min + Math.random() * (max - min);
}

function randomDir(minSpeed:number, maxSpeed:number) {
    let x = randomSpeed(minSpeed,maxSpeed);
    let y = randomSpeed(minSpeed,maxSpeed);
    return new Vector(x,y);
}

Agora sim, só precisamos criar uma classe que é um conjunto de pontos, no mínimo 2 (uma linha), que será desenhada na tela em uma determinada cor a cada atualização. Também configuramos a cor que será utilizada e se é um polígono fechado ou não. Será nossa classe Polygon.

Além disso, vamos criar 2 métodos para que o polígono mova seus pontos e se desenhe no canvas a cada atualização.

class Polygon {
    points: MovingPoint[];
    color: string;
    open: boolean;

    constructor(vectors:number,color:string,open:boolean = false) {
        if (vectors < 2) throw Error("Need at least 2 poins!");
        if (vectors == 2) this.open = true; else this.open = open;
        this.color = color;
        this.points = new Array<MovingPoint>(vectors);
        for (let i = 0; i < this.points.length; ++i) {
            this.points[i] = new MovingPoint(randomPos(width,height),randomDir(1,8));
        }
    }

    update() {
        for (let point of this.points) {
            point.move();
        }
    }

    draw(ctx:CanvasRenderingContext2D) {
        ctx.beginPath();
        ctx.strokeStyle = this.color;
        let {x,y} = this.points[0].position;
        ctx.moveTo(x,y);
        for (let i = 1; i < this.points.length; ++i) {
            let {x,y} = this.points[i].position;
            ctx.lineTo(x,y);
        }
        if (!this.open) ctx.closePath();
        ctx.stroke();
    }
}

Nesta classe usamos alguns dos vários recursos disponíveis apenas em ES2015 e TypeScript: no constructor, utilizamos generics para criar um Array de tipo específico; em update, utilizamos o novo for (let item of collection) que faz a iteração de todos os itens em uma coleção; e em draw fazemos a desconstrução do tipo Vector em 2 variáveis, para deixar o código mais legível.

Desenhando no Canvas

Agora, só precisamos criar alguns objetos Polygon, criar uma função que será chamada a cada intervalo de atualização e pronto!

let objects:Polygon[] = [
    new Polygon(2,"white"),
    new Polygon(3,"orange"),
    new Polygon(4,"rgb(100,150,255)")
];

let render = () => {
    ctx.fillStyle = "rgba(0,0,0,.075)";
    ctx.fillRect(0,0,width,height);
    for (let obj of objects) {
        obj.update();
        obj.draw(ctx);
    }
}

let renderingLoop = window.setInterval(render,33);

Note que render utiliza a nova sintaxe para criar funções de callback no ES2015/TypeScript. Esta sintaxe nova já cria os bindings necessários que no ES3 devem ser feitos manualmente, deixando o código mais legível e com menos chance de erros.

Agora é só salvar o arquivo, ver o novo arquivo canvas.js ser criado na mesma pasta do projeto, e abrir nosso html no navegador para ver o resultado!

Referências

Node.js: https://nodejs.org/

TypeScript: https://www.typescriptlang.org/

Visual Studio Code: https://code.visualstudio.com/

Canvas: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API