Typescript: aprimore aplicações com tipagem estática

Typescript: aprimore aplicações com tipagem estática
Neilton Seguins
Neilton Seguins

Compartilhe

Imagine que você está montando uma equipe de super-heróis. Você escolhe os heróis sem conhecer bem cada um deles. Por exemplo, na sua equipe tem um herói que sabe voar, mas na hora da missão você descobre que ele só consegue voar por 2 minutos e em baixa velocidade.

Agora imagine se antes de montar sua equipe você lê um dossiê de cada herói, com seus poderes, habilidades, histórico, estatísticas, etc. Você com certeza iria escolher heróis que tivessem poderes complementares, poucas fraquezas e alto índice da missão ser um sucesso.

O JavaScript seria como escolher os heróis sem conhecer bem seus poderes, e na hora da ação, ou seja, na hora do seu código funcionar, você pode ter várias surpresas. Agora, o TypeScript é diferente, com ele você consegue saber exatamente o que esperar antes, durante e depois da execução do seu código.

Se quiser entender melhor essas diferenças entre o TypeScript e o JavaScript confere este artigo Felipe. É só clicar aqui!

Neste artigo vamos conhecer mais sobre os tipos que podemos usar no TypeScript para construir aplicações mais seguras e escaláveis. Você vai aprender:

  • Tipos básicos
  • Como tipar funções
  • Asserções de tipo
  • Interfaces
  • Generics

E muito mais!

Então, Avante Vingadores!

Gif. O gif apresenta um recorte de uma cena do filme Vingadores (2012) da Marvel Studios. No gif podemos ver os heróis sendo mostrados um a um iniciando com o Hulk, Homem de Ferro, Gavião Arqueiro, Thor, Viúva Negra e Capitão América no centro da cidade de Nova York, EUA, totalmente destruída.

Tipagem estática e dinâmica

Na programação, o JavaScript é uma linguagem de tipagem dinâmica, onde as variáveis podem mudar de tipo durante a execução do código. É como ter super-heróis com habilidades que podem mudar a qualquer momento, tornando difícil prever seu comportamento.

Gif. O gif é um recorte de uma cena do filme Os Incríveis 2 (2018) da Disney Pixar, onde mostra o Sr. Incrível segurando seu filho Zézé que ao espirrar alterna entre três poderes, de fogo, relâmpago e raio laser dos olhos.

Por outro lado, o TypeScript é uma linguagem de tipagem estática, oferecendo um dossiê claro sobre o tipo de cada variável, proporcionando mais previsibilidade e facilitando o desenvolvimento sem surpresas indesejadas. Isso nos dá muito poder na hora de desenvolver nossas aplicações. Podemos usar os tipos que o Typescript nos permite para escrever um código mais confiável, com erros em tempo de desenvolvimento, futura manutenção mais fácil e o nosso código pode escalar com segurança.

Se quiser conhecer mais sobre as linguagens com tipagem estática e dinâmica clique aqui e leia o artigo incrível da nossa instrutora Juliana Amoasei.

Banner de divulgação da Imersão IA da Alura em colaboração com o Google. Mergulhe em Inteligência artificial com a Alura e o Google. Serão cinco aulas gratuitas para você aprender a usar IA na prática e desenvolver habilidades essenciais para o mercado de trabalho. Inscreva-se gratuitamente agora!

Refatorando uma aplicação com TypeScript

Agora que já sabemos as diferenças entre tipagem estática e dinâmica e suas vantagens, vamos colocar a mão no código e refatorar uma aplicação que você pode acessar clicando neste link. A aplicação é o clássico jogo da velha, ou como algumas pessoas chamam: “tic tac toe” (jogo da velha é muito melhor rsrs), como você pode ver abaixo:

Imagem. Jogo da velha, conhecido também como “tic tac toe”. Na imagem temos o título “Jogo da Velha” no topo ao centro. Logo abaixo o placar indicando os jogadores X e O. Logo abaixo do placar temos um grid 3x3 onde na primeira coluna temos três X marcados, indicando que o vencedor é o jogador X.

Esse jogo foi feito com JavaScript puro, e a gente vai refatorar ele usando o TypeScript e de quebra vamos aprender muito sobre os tipos e como usá-los em um contexto real, uma aplicação web. Então vamos nessa?

Instalando o TypeScript

Baixe o projeto inicial no link disponibilizado. Depois de baixar, recomendo que abra ele em um editor de sua preferência. Eu costumo usar o VS Code, pois ele apresenta uma integração muito boa com o TypeScript, fornecendo uma experiência de desenvolvimento muito legal, dando feedback sobre erros e variáveis declaradas e não usadas, e muito mais.

Antes de começarmos a brincadeira, precisamos do TypeScript. E para instalá-lo é bem simples. Abra um terminal no seu computador (se quiser pode fazer isso de dentro do VS Code, indo em Terminal>Novo Terminal na barra de ferramentas) e digite:

npm install -g typescript

Configurando o projeto

Com o TypeScript instalado, crie um arquivo chamado tsconfig.json. É só digitar no terminal:

tsc --init

Neste arquivo tsconfig.json você precisará fazer algumas alterações. Primeiro, mude o diretório raiz na propriedade rootDir:

"rootDir": "./src"
// … Muitas linhas depois
"outDir": "./dist"

Agora crie a pasta dist no mesmo diretório da pasta src na raíz do seu projeto. E também crie um arquivo index.ts dentro da sua pasta src. A estrutura de pastas da sua aplicação deve ficar assim:

dist
src
    |__ app.js
    |__ index.html
    |__ index.ts
    |__ styles.css
package-lock.json
package.json
tsconfig.json

Agora se você rodar o comando tsc no seu terminal, o código em TypeScript será compilado e irá gerar um arquivo JavaScript dentro da pasta dist.

dist
    |__ index.js
src
    |__ app.js
    |__ index.html
    |__ index.ts
    |__ styles.css
package-lock.json
package.json
tsconfig.json

É este arquivo index.js que iremos importar no nosso index.html na tag script , logo acima da tag de fechamento do body.

<script src="../dist/index.js"></script>

Com tudo pronto, vamos para nossa tarefa de refatorar nossa aplicação!

Tipos Básicos

Vamos iniciar criando as variáveis do nosso jogo. Para isso, eu vou escrever no meu arquivo index.ts o seguinte:

let jogoAcabou: boolean = false;
let pontuacaoX: number = 0;
let pontuacaoO: number = 0;

Os principais tipos básicos do Typescript são boolean que suportam valores verdadeiro ou falso, number, string. As variáveis do tipo number suportam valores numéricos, como inteiros, decimais, floating. Já as strings armazenam valores do tipo texto.

Se quiser conhecer mais sobre os tipos básicos do TypeScript clique aqui e leia a documentação.

Inferência de tipos

Você pode se perguntar: “ Eu sempre tenho que explicitar o tipo? ” De preferência sim, porque fica mais claro, seu código fica legível. Mas o TypeScript é muito esperto e ele usa uma maneira de inferir o tipo de uma variável apenas pela sua declaração. Exemplo:

const numero = 42; // TypeScript infere o tipo 'number'
const estaChovendo = false; // TypeScript infere o tipo 'boolean'

const numeros = [1, 2, 3, 4, 5]; // TypeScript infere o tipo 'number[]'
const palavras = ["maçã", "banana", "laranja"]; // TypeScript infere o tipo 'string[]'

const usuario = {
  nome: "João",
  idade: 25,
  isAdmin: false,
}; // TypeScript infere o tipo { nome: string, idade: number, isAdmin: boolean }

function soma(a: number, b: number) {
  return a + b;
} // TypeScript infere a assinatura: (a: number, b: number) => number

const resultado = soma(10, 5); // TypeScript infere resultado como tipo 'number'

A inferência de tipos é uma característica poderosa do TypeScript que permite ao compilador deduzir automaticamente os tipos de variáveis com base no contexto.

Se quiser conhecer mais sobre inferência de tipos no TypeScript clique aqui e leia a documentação.

Union

Se por um acaso você precisar combinar tipos, você pode usar Unions. Union permite combinar um ou mais tipos. Sua sintaxe é um pouco diferente dos outros tipos, ele utiliza uma barra vertical para passar os tipos que ele deve aceitar. Exemplo:

Suponha que você tenha um botão em HTML e queira alternar entre estilos diferentes usando TypeScript.

<!-- HTML -->
<button id="meu-botao">Clique em mim</button>

Então você faz:

// TypeScript
const botao = document.getElementById('meu-botao');
// Union Type para representar diferentes estados do botão
type EstadoBotao = 'default' | 'clicked';
let currentState: EstadoBotao = 'default';
botao.addEventListener('click', () => {
  estatdoAtual = estatdoAtual === 'default' ? 'clicked' : 'default';
  // Aplicar estilos com base no estado
  botao.className = estatdoAtual === 'default' ? 'default-style' : 'clicked-style';
});

Esse código faz com que ao ser clicado os estilos do botão alterem com base nas classes default-style e clicked-style. Também é possível usar outros tipos em Union, como number, boolean, e até arrays.

Se quiser conhecer mais sobre Union no TypeScript clique aqui e leia a documentação.

Notou que foi usada a palavra reservada type para criar o tipo EstadoBotao? Vamos descobrir como ela funciona e como usar Union em nossos códigos.

Vamos lá!

Types

Continuando nosso código, também é possível definir um tipo customizado usando a palavra reservada type. Por exemplo:

type SimboloJogador = "X" | "O";
let jogadorAtual: SimboloJogador = "X";

Neste trecho, criamos o tipo SimboloJogador que, onde usamos Union para que esse tipo possa receber dois valores (que por sua vez são valores do tipo string). O tipo SimboloJogador pode ser tanto “X” quanto “O” e usamos ele para inferir o tipo da variável jogadorAtual, passando como valor inicial o valor “X”.

Também podemos definir um type como um objeto, onde podemos definir quais propriedades esse objeto vai ter e quais são os tipos dessas propriedades. Exemplo:

type Jogador = {
  nome: string;
  simbolo: SimboloJogador;
}

Aqui a gente definiu um type Jogador, que tem duas propriedades: nome e simbolo. Onde os tipos de cada propriedade são string e SimboloJogador, que definimos anteriormente. Lembrando que todos os tipos vistos anteriormente podem ser usados para tipar uma dessas propriedades.

Se quiser conhecer mais sobre type aliases no TypeScript clique aqui e leia a documentação.

Any

Este tipo é um dos que podem causar mais confusões para quem está iniciando com o TypeScript. Se imaginarmos que o TypeScript já é tipado e que nós podemos usar union em casos de mais de um valor, qual a utilidade do any? Entendo a sua dúvida, porque foi uma dúvida que tive quando comecei a estudar TypeScript. Então, para tentar esclarecer o uso do any, imagine que você está criando uma aplicação que vai receber os dados de uma API, por exemplo. E por mais que você tenha estudado a documentação da API, você não sabe exatamente o que vai receber de dados e os tipos de cada coisa. Esse é um dos cenários que usamos o any. Exemplo:

const apiResposta: any = await fetch('https://api.example.com/data');
const dado: any = await apiResposta.json();
console.log(dado);

Embora idealmente você deveria criar definições de tipos para a resposta da API, pode haver situações em que isso não é prático ou possível imediatamente.

Outro exemplo comum, são algumas bibliotecas de gráficos em JavaScript, que podem não ter suporte, ou não apresentam suporte total a TypeScript. Ao integrar essas bibliotecas, você pode recorrer ao uso do any:

var Chart: any;

// Uso do tipo any para criar um gráfico
const myChart: any = new Chart('myCanvas', {
  type: 'bar',
  data: {
    labels: ['Red', 'Blue', 'Yellow'],
    datasets: [{
      label: '# of Votes',
      data: [12, 19, 3],
    }],
  },
});

Só que não é boa prática sair por aí tipando as coisas como any. Isso faz com que você anule o benefício da tipagem forte que o TypeScript adiciona ao JavaScript. Além de que o uso excessivo do Any pode gerar comportamentos e efeitos colaterais indesejados. E, principalmente, ao utilizar o any de forma excessiva você está deixando de obter todos os benefícios que o TypeScript pode trazer para sua aplicação. Use-o com sabedoria.

Se quiser conhecer mais sobre o tipo any do TypeScript clique aqui e leia a documentação.

Arrays

Também podemos tipar Arrays. Para tipar um Array podemos usar a notação de colchetes [] ou a palavra reservada Array<>. Exemplo:

let numeros: number[] = [1, 2, 3];
let textos: string[] = ["exemplo 1", "exemplo 2", "exemplo 3"];

No nosso código, vamos criar um Array para o tabuleiro do nosso jogo da velha. Ficando assim:

let tabuleiro: Array<SimboloJogador | null> = Array(9).fill(null);

A variável tabuleiro será um Array que terá os tipos: “X” ou “O” ou null. Os tipos “X” ou “O” vem do nosso tipo customizado SimboloJogador. E para essa variável, a gente atribui um Array de 9 posições, ou seja, um tabuleiro 3x3.

Se quiser conhecer mais sobre como tipar arrays no TypeScript clique aqui e leia a documentação.

Tuplas

Agora a gente começa a entrar em tipos e estruturas um pouco mais complexas. A primeira delas são as Tuplas. Tuplas são estruturas de dados semelhantes a Arrays, com a diferença que em Arrays a gente armazena apenas um tipo de dado, enquanto que nas Tuplas a gente consegue armazenar diferentes tipos. Exemplo:

let lista: [string, number, string] = ['string', 1, 'string 2'];

E da forma que definimos os tipos, nossa variável precisa obedecer. Se por exemplo mudarmos a ordem dos elementos 0 e 1 da Tupla teremos um erro, pois ficaríamos com uma estrutura [number, string, number] que é diferente do tipo da variável lista: [string, number, number].

Legal, e como podemos usar as Tuplas no nosso projeto? Para exemplificar o uso das Tuplas eu optei por fazer o seguinte:

let tuplaVencedora: [number, number, number];
type CombinacaoVencedora = typeof tuplaVencedora;

Eu defini uma variável chamada tuplaVencedora que tem três posições. Depois eu criei um tipo chamado de CombinacaoVencedora que possui o mesmo tipo que a tuplaVencedora. Note que usamos uma nova palavra reservada, o typeof. Esse operador já existe no JavaScript, e no TypeScript ele é usado em um contexto de tipo, para se referir ao tipo de uma variável. É por isso que CombinacaoVencedora terá o mesmo tipo que a variável tuplaVencedora.

Você pode se perguntar: "Mas para que usar Tuplas para tipar uma outra variável se eu posso fazer direto?"

Você está certo, podemos fazer direto e inferir o tipo da variável CombinacaoVencedora assim:

type CombinacaoVencedora = [number, number, number];

O que está correto e eliminaria quaisquer redundâncias. Porém, como recurso didático para exemplificar o uso das Tuplas optamos por fazer como no primeiro exemplo.

Se quiser conhecer mais sobre tuplas no TypeScript clique aqui e leia a documentação.

Enum

Antes de criarmos as funções do nosso jogo, vamos falar um pouco sobre Enum. Enum nos permite declarar um conjunto de valores/constantes predefinidas. Por exemplo:

enum FinalDeSemana {
    Sexta = "Sexta-feira",
    Sabado = "Sabado",
    Domingo = "Domingo"
}

E para usar um Enum, você pode fazer:

console.log(FinalDeSemana.Sexta) // Sexta-feira

No nosso jogo, vamos usar enum para controlar o status do jogo, se ele está acontecendo, se teve uma vitória de algum jogador ou se houve um empate. Vamos fazer isso da seguinte forma:

enum StatusJogo {
  EmAndamento = "EM_ANDAMENTO",
  Vitoria = "VITORIA",
  Empate = "EMPATE",
}

Agora vamos criar uma variável para controlar esse status do jogo

let statusJogo: StatusJogo = StatusJogo.EmAndamento;

Note que usamos o enum para tipar a nossa variável statusJogo. Esse é um recurso muito legal do Typescript para que possamos conseguir tipar as variáveis de nossas aplicações.

Se quiser conhecer mais sobre Enum no TypeScript clique aqui e leia a documentação.

Type Assertions

Os Type Assertions, ou em tradução livre, Asserções de tipo, é uma forma de alterar o tipo de uma variável sem que o compilador reclame, ou seja, sem que o compilador envie uma exception. É como o cast no Java e em outras linguagens.

Para que fique claro, imagine que você tem uma função que recebe um parâmetro do tipo any. Porém, no corpo dessa função, você tem uma variável do tipo string que irá receber o valor desse parâmetro. Como resolver? A gente pode fazer uma asserção de tipo da seguinte maneira:

function typeAssertionExample (parametroAny: any) {
    let variavelString: string = <string>parametroAny
    return variavelString
}

A gente usa a notação: <tipo-da-assercao> para alterar o tipo de uma variável.

Tem outra maneira de fazer uma asserção de tipo, que é a que vamos fazer no código do nosso jogo. Vou precisar manipular o DOM para buscar as células do nosso jogo, onde as pessoas irão preencher com “X” ou “O” para quando eu criar a função de reiniciar o jogo, eu consiga limpar as células. Então eu posso fazer:

const elementoTabuleiro = document.getElementById("board");
const celulas = Array.from(elementoTabuleiro.querySelectorAll(".celula"));

Porém, para garantir uma tipagem forte no meu código e garantir que eu vou estar trabalhando com elementos do DOM, eu posso fazer uma asserção de tipo para essas variáveis, dessa forma:

const elementoTabuleiro = document.getElementById("board") as HTMLDivElement;
const celulas = Array.from(
  elementoTabuleiro.querySelectorAll(".celula")
) as Array<HTMLDivElement>;

Neste trecho de código, usei a palavra reservada as para fazer uma asserção de tipo. Com isso, agora eu garanto que as minhas variáveis elementoTabuleiro e celulas sejam do tipo nativo HTMLDivElement.

Se quiser conhecer mais sobre os tipos básicos do TypeScript clique aqui e leia a documentação.

Legal né? São muitos recursos que temos do TypeScript que nos ajudam a tipar nossas aplicações evitando assim possíveis erros indesejados em tempo de compilação. Mas ainda tem alguns que não falamos, como as funções.

Elas são as próximas, vamos nessa!

Tipando funções

No TypeScript podemos tipar funções, informando ao compilador qual o tipo que elas devem retornar. Podemos tipar também os parâmetros que elas recebem. Exemplo:

function soma (x: number, y: number) {
    return x + y
}

Essa função retorna um número. Só que podemos deixar explícito o tipo de dado que ela retorna. Por exemplo:

function soma (x: number, y: number): number {
    return x + y
}

Se você quiser que sua função retorne uma string sem mudar os parâmetros, terá que mudar o seu retorno, pois o TypeScript é inteligente para saber que a soma de dois números tem que dar um número. Exemplo:

function soma (x: number, y: number): string {
    return `Resultado: ${x + y}`
}

Se quiser conhecer mais sobre como trabalhar com funções no TypeScript clique aqui e leia a documentação.

Void

Agora que sabemos como tipar nossas funções, vamos construir as funções do nosso jogo. Começando pela função que atualiza o placar. Ela não recebe nenhum parâmetro e nem retorna nada, então podemos tipar ela simplesmente como void. Exemplo:

function reiniciarJogo(): void {
  tabuleiro.fill(null);
  jogoAcabou = false;
  statusJogo = StatusJogo.EmAndamento;
  celulas.forEach((celula) => (celula.textContent = ""));
  atualizarPontuacao();
}

O void, diferente do any que espera retorno de qualquer valor, informa ao compilador que esta função não retorna valor nenhum.

Agora vamos para a função de atualizarPontuacao. Assim como a função de reiniciar o jogo, essa função também não recebe e nem retorna nada, logo ela será do tipo void:

function atualizarPontuacao(): void {
  document.getElementById(
    "pontuacao"
  )!.textContent = `Jogador (X) - ${pontuacaoX} | Jogador (O) - ${pontuacaoO}`;
}

E por fim, a função que verifica o vencedor do jogo. Vamos fazê-la por etapas.

1 - Primeiro a gente cria uma matriz com as combinações de vitória que os jogadores têm, ou seja, três elementos iguais, alinhados verticalmente, horizontalmente ou na diagonal configuram uma vitória para o jogador. Então fica assim:

function verificarVencedor(): void {
  const combinacoesVitoria: CombinacaoVencedora[] = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6],
  ];
}

Note que estamos usando o tipo CombinacaoVencedora, que definimos com o auxílio de uma Tupla. Só que a variável combinacoesVitoria é um Array desse tipo, então a gente precisa usar os colchetes ([]).

2 - O próximo passo é percorrer cada célula e verificar se temos um vencedor. Se tivermos, a gente altera o status do jogo para vitória, informa que o jogo acabou e declara um vencedor. Também atualizamos a pontuação para cada jogador. E depois de algum tempo a gente informa o jogador que venceu em um alert no navegador e reinicia o jogo mantendo o placar atual.

for (const combinacao of combinacoesVitoria) {
    const [a, b, c] = combinacao;
    if (
      tabuleiro[a] &&
      tabuleiro[a] === tabuleiro[b] &&
      tabuleiro[a] === tabuleiro[c]
    ) {
      statusJogo = StatusJogo.Vitoria;
      jogoAcabou = true;
      const vencedor = jogadorAtual === "X" ? `Jogador X` : `Jogador O`;
      if (jogadorAtual === "X") {
        pontuacaoX++;
      } else {
        pontuacaoO++;
      }
      setTimeout(() => {
        alert(`${vencedor} venceu!`);
        reiniciarJogo();
      }, 100);
      return;
    }
  }

3 - Também precisamos verificar se houve empate no jogo. Se sim, a gente vai mudar o status do jogo para empate, acabar o jogo e depois de um tempo anunciar o empate e reiniciar o jogo.

 if (!tabuleiro.includes(null)) {
    statusJogo = StatusJogo.Empate;
    jogoAcabou = true;
    setTimeout(() => {
      alert("Empate!");
      reiniciarJogo();
    }, 100);
  }

Se quiser conhecer mais sobre o tipo void no TypeScript clique aqui e leia a documentação.

Finalizando o Jogo

A parte final do jogo não é muito diferente do nosso arquivo app.js.

document.querySelectorAll(".celula").forEach((celula, indice) => {
  celula.addEventListener("click", () => {
    if (!jogoAcabou && !tabuleiro[indice]) {
      tabuleiro[indice] = jogadorAtual;
      celula.textContent = jogadorAtual;
      verificarVencedor();
      jogadorAtual = jogadorAtual === "X" ? "O" : "X";
    }
  });
});

window.onload = reiniciarJogo;

A gente vai percorrer cada célula e quando ela for clicada a gente marca ela com o valor do jogador atual, ou seja, X ou O. Depois a gente chama a função que verifica o vencedor, e altera a vez do jogador, isto é, se o jogador X já jogou a vez passa para o jogador O e assim por diante.

Para você salvar e transpilar o código TypeScript, salve o arquivo index.ts e rode o seguinte comando no terminal:

tsc

E volte ao navegador para ver o resultado!!

Se você quiser o código completo da aplicação, clique aqui neste link e acesse o projeto!

Indo mais fundo

Você pode estar pensando que é muita coisa e que provavelmente você tem recursos mais que suficientes para criar aplicações fortemente tipadas com TypeScript, mas sinto te informar que ainda não mergulhamos fundo o suficiente na linguagem. Por exemplo, nossa aplicação está refatorada, mais segura e funcionando, mas poderíamos deixá-la melhor adicionando, por exemplo, uma tela inicial para que os jogadores possam colocar seus nomes, algo que melhore ainda mais a experiência do usuário.

Não vamos fazer tudo aqui, mas quero te desafiar a fazer. Por hora, vou aproveitar e falar sobre outros dois recursos muito importantes do TypeScript. Vamos lá?

Interfaces

Imagine que você implementou a tela inicial do jogo. Nela você permite que os jogadores insiram seu nome, como mostra a imagem abaixo:

Tela inicial do jogo. Nela podemos ver alinhados ao centro o título, logo abaixo dois campos de entrada para o nome dos jogadores, com o primeiro campo para o jogador X e o segundo campo para o jogador O. Logo abaixo dos campos um botão com o texto “Iniciar jogo”

E para isso você quer capturar os dados do jogador, que são basicamente o nome e o símbolo que ele escolheu, X ou O.

Para fazer isso, podemos usar interfaces. Então poderíamos fazer o seguinte:

interface Jogador {
  nome: string;
  simbolo: SimboloJogador;
}

Neste código a gente criou uma estrutura para o jogador com o nome e o símbolo dele. Com os tipos que já conhecemos. Notou alguma semelhança com alguma estrutura do JavaScript? Se não, vou te contar.

Interfaces são abstrações do TypeScript, um conjunto de métodos e propriedades que descrevem objetos, porém não implementam e nem os inicializam. Por isso, a semelhança com os objetos do JavaScript.

Você pode tá se perguntando: "Mas no que interfaces são diferentes de Types? Eu poderia definir o tipo jogador dessa maneira?"

type Jogador = {
  nome: string;
  simbolo: SimboloJogador;
}

E essa é uma excelente pergunta! types e Interfaces são muito parecidos, e fica a seu critério usar um ou outro. Mas tem coisas que podemos fazer com um e não com outro. Por exemplo, se quisermos que uma propriedade do nosso tipo jogador seja apenas leitura, não podemos fazer isso com Type, mas com interface sim. Veja:

interface Jogador {
  readonly nome: string;
  simbolo: SimboloJogador;
}

Neste exemplo, usamos a palavra reservada readonly, que diz que nossa propriedade é apenas leitura, ou seja, não poderemos alterar seu valor pois ele é apenas para consulta/leitura. Ao utilizar o type, esse recurso não é possível.

Se quiser conhecer mais sobre as interfaces no TypeScript clique aqui e leia a documentação.

Generics

Indo além na tipagem com o TypeScript, imagina que ao invés de tipar de forma estática suas variáveis e funções, você gostaria de informar esse tipo de forma dinâmica. Isso mesmo, você consegue fazer uma tipagem forte no seu código de forma dinâmica também. Como fazer isso?

Imagine que agora que você tem como capturar o nome dos jogadores, você quer também que esses nomes apareçam no placar, com a pontuação adequada para cada jogador. Então vamos dar uma refatorada na nossa função atualizarPontuacao().

function atualizarPontuacao<T extends Jogador>(jogador1: T, jogador2: T): void {
  document.getElementById(
    "pontuacao"
  )!.textContent = `${jogador1.nome} (X) - ${pontuacaoX} | ${jogador2.nome} (O) - ${pontuacaoO}`;
}

A função de atualizar pontuação agora recebe dois parâmetros, que são os jogadores, que por sua vez tem um nome e símbolo. E essa função coloca esses nomes dos jogadores no placar de acordo com o símbolo que cada jogador escolheu. Você deve ter reparado a notação:

atualizarPontuacao<T extends Jogador>(jogador1: T, jogador2: T)

Isso é o que chamamos de Generics! Os generics são uma forma de passar parâmetros de tipos, permitindo tipar variáveis e funções de forma dinâmica. Na nossa função o <T extends Jogador> significa que o tipo T, que é o parâmetro de tipo que vamos tornar dinâmico, vai herdar as mesmas propriedades do tipo Jogador (o extends é uma outra maneira de fazer o type assertion). E com esse tipo T eu vou tipar os parâmetros da minha função, que são os dois jogadores do game.

Ficou difícil? Vamos ver mais exemplos de Generics!

Suponha que você esteja armazenando dados localmente usando o LocalStorage. Você pode usar Generics para garantir que o tipo do valor armazenado e recuperado seja o mesmo.

function salvarNoLocalStorage<T>(chave: string, valor: T): void {
  const valorSerializado = JSON.stringify(valor);
  localStorage.setItem(chave, valorSerializado);
}

function obterDoLocalStorage<T>(chave: string): T | null {
  const valorSerializado = localStorage.getItem(chave);
  return valorSerializado ? JSON.parse(valorSerializado) : null;
}

// Exemplo de uso
salvarNoLocalStorage("usuario", { nome: "Alice", idade: 30 });
const usuarioSalvo = obterDoLocalStorage<{ nome: string, idade: number }>("usuario");

É possível passar mais tipos como parâmetro ao usar Generics.

Se quiser conhecer mais sobre manipuladores de tipos no TypeScript clique aqui e leia a documentação.

Conclusão

Poxa, quanta coisa legal que vimos neste artigo, não é mesmo?

Aprendemos sobre as diferenças entre o JavaScript e o TypeScript, linguagens de tipagem estática e dinâmica respectivamente, aprendemos como instalar o TypeScript em nosso projeto, e enquanto refatoramos o projeto com o TypeScript conhecemos os tipos básicos e avançados, tudo isso no contexto de uma aplicação real, o famoso “jogo da velha”. Vimos como usar Types, Enum, Union, Interfaces, como tipar funções e como usar Generics para permitir uma tipagem forte, porém de uma forma dinâmica. Você pode acessar o projeto final completo clicando neste link!

Agora que você já sabe como usar os principais tipos, que tal aprender mais sobre como usar o TypeScript em suas aplicações Front end? Para isso te convido a conferir as nossas formações abaixo:

Espero que você tenha gostado! E seria muito bacana ver a sua versão do jogo nas redes sociais. Então pegue o projeto, aplique o TypeScript e use a sua criatividade para criar mais funcionalidade e estilos, e compartilhe com a gente, marque os perfis da Alura nas redes sociais e usa a #AprendiNaAlura. Ficaremos felizes em ver os seus resultados.

Até a próxima!

Neilton Seguins
Neilton Seguins

Sou graduado como Bacharel em Ciência e Tecnologia e em Engenharia Mecânica. Atuo como Instrutor de Desenvolvedor de Software na Alura e possuo experiência com desenvolvimento usando JavaScript/TypeScript, React js, Next js e Node.js. Amo compartilhar conhecimento, pois acredito que a educação é transformadora e quero mudar a vida de pessoas através da educação assim como consegui mudar de vida. Também amo ouvir e tocar música, ler livros e mangás e assistir séries.

Veja outros artigos sobre Front-end