Pular para o conteúdo

Introdução ao TypeScript

O que é TypeScript?

TypeScript é uma linguagem de programação fortemente tipada que se baseia no JavaScript. Foi originalmente projetada por Anders Hejlsberg em 2012 e é atualmente desenvolvida e mantida pela Microsoft como um projeto de código aberto.

O TypeScript compila para JavaScript e pode ser executado em qualquer ambiente de execução JavaScript (por exemplo, um navegador ou Node.js em um servidor).

Ele oferece suporte a múltiplos paradigmas de programação, como programação funcional, genérica, imperativa e orientada a objetos, e é uma linguagem compilada (transpilada) que é convertida em JavaScript antes da execução.

Por que TypeScript?

TypeScript é uma linguagem fortemente tipada que ajuda a prevenir erros comuns de programação e a evitar certos tipos de erros em tempo de execução antes que o programa seja executado.

Uma linguagem fortemente tipada permite ao desenvolvedor especificar várias restrições e comportamentos do programa nas definições de tipos de dados, facilitando a verificação da correção do software e a prevenção de defeitos. Isso é especialmente valioso em aplicações de grande escala.

Alguns dos benefícios do TypeScript:

  • Tipagem estática, opcionalmente fortemente tipada
  • Inferência de tipos
  • Acesso a recursos ES6 e ES7
  • Compatibilidade multiplataforma e entre navegadores
  • Suporte de ferramentas com IntelliSense

TypeScript e JavaScript

O código TypeScript é escrito em arquivos .ts ou .tsx, enquanto o código JavaScript é escrito em arquivos .js ou .jsx.

Arquivos com a extensão .tsx ou .jsx podem conter JSX, uma extensão da sintaxe do JavaScript usada no React para o desenvolvimento de interfaces de usuário.

O TypeScript é um superconjunto tipado de JavaScript (ECMAScript 2015) em termos de sintaxe. Todo código JavaScript é código TypeScript válido, mas o inverso nem sempre é verdadeiro.

Por exemplo, considere uma função em um arquivo JavaScript com a extensão .js, como a seguinte:

const sum = (a, b) => a + b;

A função pode ser convertida e usada no TypeScript alterando a extensão do arquivo para .ts. No entanto, se a mesma função for anotada com tipos TypeScript, ela não poderá ser executada em nenhum ambiente de execução JavaScript sem compilação. O seguinte código TypeScript produzirá um erro de sintaxe se não for compilado:

const sum = (a: number, b: number): number => a + b;

O TypeScript foi projetado para detectar, em tempo de compilação, possíveis erros em tempo de execução, permitindo que os desenvolvedores expressem sua intenção por meio de anotações de tipo. Além disso, graças à inferência de tipos, o TypeScript também pode detectar certos problemas mesmo quando nenhuma anotação de tipo explícita é fornecida. Por exemplo, o seguinte trecho de código não especifica nenhum tipo TypeScript:

const items = [{ x: 1 }, { x: 2 }];
const result = items.filter(item => item.y);

Neste caso, o TypeScript detecta um erro e informa:

Property 'y' does not exist on type '{ x: number; }'.

O sistema de tipos do TypeScript é amplamente influenciado pelo comportamento do JavaScript em tempo de execução. Por exemplo, o operador de adição (+), que no JavaScript pode realizar a concatenação de strings ou a adição numérica, é modelado da mesma forma no TypeScript:

const result = '1' + 1; // O resultado é do tipo string

A equipe responsável pelo TypeScript tomou a decisão deliberada de sinalizar usos incomuns do JavaScript como erros. Por exemplo, considere o seguinte código JavaScript válido:

const result = 1 + true; // Em JavaScript, o resultado é igual a 2

No entanto, o TypeScript aponta um erro:

Operator '+' cannot be applied to types 'number' and 'boolean'.

Este erro ocorre porque o TypeScript impõe estritamente a compatibilidade de tipos e, neste caso, identifica uma operação inválida entre um número e um booleano.

Geração de Código pelo TypeScript

O compilador TypeScript tem duas responsabilidades principais: verificar se há erros de tipo e compilar para JavaScript. Esses dois processos são independentes um do outro. Os tipos não afetam a execução do código em um ambiente de execução JavaScript, pois são completamente apagados durante a compilação. O TypeScript ainda pode gerar JavaScript mesmo na presença de erros de tipo. Aqui está um exemplo de código TypeScript com um erro de tipo:

const add = (a: number, b: number): number => a + b;
const result = add('x', 'y'); // Um argumento do tipo 'string' não é atribuível a um parâmetro do tipo 'number'.

No entanto, ele ainda pode produzir uma saída JavaScript executável:

'use strict';
const add = (a, b) => a + b;
const result = add('x', 'y'); // xy

Não é possível verificar tipos TypeScript em tempo de execução. Por exemplo:

interface Animal {
name: string;
}
interface Dog extends Animal {
bark: () => void;
}
interface Cat extends Animal {
meow: () => void;
}
const makeNoise = (animal: Animal) => {
if (animal instanceof Dog) {
// 'Dog' refere-se apenas a um tipo, mas está sendo usado como valor aqui.
// ...
}
};

Como os tipos são apagados após a compilação, não há como executar este código em JavaScript. Para reconhecer tipos em tempo de execução, precisamos usar outro mecanismo. O TypeScript fornece várias opções, sendo uma comum a união marcada (tagged union). Por exemplo:

interface Dog {
kind: 'dog'; // União marcada
bark: () => void;
}
interface Cat {
kind: 'cat'; // União marcada
meow: () => void;
}
type Animal = Dog | Cat;
const makeNoise = (animal: Animal) => {
if (animal.kind === 'dog') {
animal.bark();
} else {
animal.meow();
}
};
const dog: Dog = {
kind: 'dog',
bark: () => console.log('bark'),
};
makeNoise(dog);

A propriedade “kind” é um valor que pode ser usado em tempo de execução para distinguir entre objetos em JavaScript.

Também é possível que um valor em tempo de execução tenha um tipo diferente daquele declarado na declaração de tipo. Por exemplo, se o desenvolvedor interpretou mal um tipo de API e o anotou incorretamente.

O TypeScript é um superconjunto do JavaScript, portanto a palavra-chave “class” pode ser usada como um tipo e valor em tempo de execução.

class Animal {
constructor(public name: string) {}
}
class Dog extends Animal {
constructor(
public name: string,
public bark: () => void
) {
super(name);
}
}
class Cat extends Animal {
constructor(
public name: string,
public meow: () => void
) {
super(name);
}
}
type Mammal = Dog | Cat;
const makeNoise = (mammal: Mammal) => {
if (mammal instanceof Dog) {
mammal.bark();
} else {
mammal.meow();
}
};
const dog = new Dog('Fido', () => console.log('bark'));
makeNoise(dog);

No JavaScript, uma “classe” tem uma propriedade “prototype”, e o operador “instanceof” pode ser usado para testar se a propriedade prototype de um construtor aparece em qualquer lugar na cadeia de protótipos de um objeto.

O TypeScript não tem efeito no desempenho em tempo de execução, pois todos os tipos serão apagados. No entanto, o TypeScript introduz alguma sobrecarga durante o processo de build.

JavaScript Moderno Hoje (Downleveling)

O TypeScript pode compilar código para qualquer versão lançada do JavaScript desde o ECMAScript 3 (1999). Isso significa que o TypeScript pode transpilar o código dos recursos JavaScript mais recentes para versões mais antigas, um processo conhecido como Downleveling. Isso permite o uso do JavaScript moderno, mantendo a compatibilidade máxima com ambientes de execução mais antigos.

Durante a transpilação para uma versão mais antiga do JavaScript, o TypeScript pode gerar código com um custo adicional de desempenho em comparação com as implementações nativas.

Aqui estão alguns dos recursos modernos do JavaScript que podem ser usados no TypeScript:

  • Módulos ECMAScript em vez de callbacks “define” no estilo AMD ou instruções “require” do CommonJS.
  • Classes em vez de protótipos.
  • Declaração de variáveis usando “let” ou “const” em vez de “var”.
  • Loop “for-of” ou “.forEach” em vez do loop “for” tradicional.
  • Funções de seta (arrow functions) em vez de expressões de função.
  • Atribuição por desestruturação (destructuring assignment).
  • Nomes de propriedade/método abreviados e nomes de propriedade computados.
  • Parâmetros de função com valores padrão.

Ao aproveitar esses recursos modernos do JavaScript, os desenvolvedores podem escrever código mais expressivo e conciso no TypeScript.