Typescript
TypeScript
TypeScript é uma linguagem de programação tipada open-source desenvolvida pela Microsoft que adiciona tipagem estática opcional ao JavaScript. Foi lançada em 2012 com o objetivo de facilitar o desenvolvimento de aplicações JavaScript complexas e escaláveis.
História
TypeScript foi criada por Anders Hejlsberg, arquiteto líder da linguagem C# na Microsoft. A motivação por trás do TypeScript era resolver alguns dos problemas associados com JavaScript no desenvolvimento em larga escala, como falta de tipagem estática, suporte inadequado para abstração de classes e métodos, entre outros.
Com o amadurecimento do ecossistema JavaScript e aumento na complexidade das aplicações web, a necessidade de ferramentas para ajudar a gerenciar essa complexidade se tornou aparente. O TypeScript serviu esse propósito provendo tipagem estática durante o desenvolvimento, bem como ferramentas de refatoração e autocompletar.
Documentário sobre a História do Typescript
Funcionalidades principais
O TypeScript adiciona as seguintes funcionalidades principais ao JavaScript:
-
Sistema de tipagem estática e inferência de tipos - Permite tipar variáveis, parâmetros e valores de retorno de funções. Isso ajuda a detectar erros em tempo de desenvolvimento. O TypeScript também pode inferir tipos com base no uso em certos contextos.
-
Suporte a orientação a objetos - Adiciona conceitos de OOP como classes, interfaces, herança, modificadores de acesso e etc. Isso permite uma estruturação melhor de código JavaScript.
-
Novos recursos ECMAScript mais recentes - O TypeScript pode incorporar recursos de versões futuras do JavaScript/ECMAScript antes que sejam disponibilizadas nativamente nos navegadores e interpretadores.
-
Compila para JavaScript multipVersão - O código TypeScript é convertido para JavaScript vanilla durante a compilação, permitindo compatibilidade com diversas versões do JavaScript.
-
Configurável via tsconfig.json - Permite controlar e ajustar características e configurações de compilação através de um arquivo de configuração.
TypeScript continua se popularizando rapidamente e sendo adotado por aplicações web e mobile de grande escala devido as suas funcionalidades que melhoram a qualidade e escalabilidade de código JavaScript.
Create React App
Iniciando um novo projeto
Para iniciar um projeto React + TS utilizando o create-react-app, basta utilizar o comando:
npx create-react-app my-app --template typescript
# ou
yarn create-react-app my-app --template typescript
Adicionando a um projeto existente
Para adicionar o Typescript a um projeto CRA existente, adicione as seguintes libs:
npm install --save typescript @types/node @types/react @types/react-dom @types/jest
# ou
yarn add typescript @types/node @types/react @types/react-dom @types/jest
Execute o comando abaixo para gerar o tsconfig.json (arquivo de configuração do Typescript):
npx tsc --init
# ou
yarn run tsc --init
Ele virá com as configuração padrão. Caso tenha interesse em saber mais sobre isso, clique aqui
E renomeie os arquivos Javascript para Typescript seguindo a regra:
- Caso o arquivo possua extensão .jsx ou conteúdo JSX, renomeie para .tsx
- Caso o arquivo não entre no critério acima, renomeie para .ts
Com isso, seu projeto deve começar a acusar os erros de tipagem e você está pronto para utilizar o TS.
Tipos
Básico
Os 3 tipos básicos mais conhecidos são:
- boolean: valores true ou false;
const isThisAGoodDoc: boolean = true;
- number: valores numéricos;
const fightingPower: number = 9001;
- string: valores textuais;
const rocketseat: string = "Are you ready for launch?";
Além dessas, temos outras tipagens básicas que não muito convencionais:
- any: aceita qualquer valor. Utilizado quando não queremos fazer a checagem do tipo;
- void: é basicamente o oposto de any, utilizado principalmente para demarcar quando não queremos retornar valores de uma função (mesmo assim, ao utilizar void a função irá retornar undefined, explicitamente ou implicitamente);
- null: aceita valores do tipo null;
- undefined: aceita valores do tipo undefined;
- never: não aceita nenhum tipo, utilizada principalmente para funções que nunca devem retornar algo (funções sem retorno retornam undefined, por isso usamos void) como loops infinitos ou excessões.
Avançado
Não se deixe enganar pelo título dessa seção, avançado não significa complexo. A partir das tipagens que vimos anteriormente, podemos utilizar alguns recursos do Typescript para expandir as tipagens do nosso código. As mais utilizadas são:
Arrays
Temos duas formas principais de declará-los: adicionando [] ao final do tipo ou utilizando o generic mais sobre isso nas próximas seções). Exemplo:
const educationTeam: string[] = ["Vini", "Dani", "Doge", "Claudião", "Graciano"];
const educationTeam: Array<string> = ["Vini", "Dani", "Doge", "Claudião", "Graciano"];
Tuples
Utilizado quando queremos trabalhar com arrays que sabemos exatamente quantos elementos ele terá, mas que não serão necessariamente do mesmo tipo. Exemplo:
const eitaGiovanna: [string, boolean] = ["O forninho caiu?", true]
Onde temos um array com 2 elementos, onde o primeiro é uma string e o segundo um boolean.
Enums
Utilizado quando queremos dar um nome mais amigável a um conjunto de valores. Exemplo:
enum Techs {
React,
Angular,
Vue
};
const theBest: Techs = Techs.React;
console.log(theBest) // Irá printar o valor 0
Objetos
Apesar de ser possível descrever um objeto utilizando simplesmente o object, não é recomendado pois dessa forma não conseguimos definir os campos, a sua forma (shape).
Funções
No caso das funções, precisamos definir a tipagem dos argumentos e do retorno. Exemplos:
function overkillConsoleLog(arg1: string, arg2: number): void {
console.log(arg1, arg2);
}
function anotherCallbackExample(callback: (arg: number) => string): string {
return callback(9);
}
No primeiro exemplo temos uma função chamada overkillConsoleLog que recebe dois argumentos: arg1 é uma string e arg2 é um number. Como não queremos retornar nenhum valor da função, atribuímos o tipo void ao retorno.
No segundo exemplo, declaramos uma função chamada anotherCallbackExample que recebe um parâmetro callback que representa uma função. Essa função recebe um argumento chamado arg do tipo number e retorna uma string. Como na função anotherCallbackExample estamos retornando diretamente o valor de callback, atribuímos também ao retorno dela o tipo string.
Aqui está uma visão geral de classes, herança e interfaces em TypeScript traduzido para português:
Classes
Classes em TypeScript fornecem uma maneira de definir componentes reutilizáveis usando conceitos de programação orientada a objetos como encapsulamento, herança e polimorfismo. Elas suportam coisas como construtores, propriedades, métodos, modificadores de acesso, etc.
Por exemplo:
class Pessoa {
nome: string;
constructor(nome: string) {
this.nome = nome;
}
cumprimentar() {
console.log(`Olá, meu nome é ${this.nome}`);
}
}
let pessoa = new Pessoa("João");
pessoa.cumprimentar();
Herança usando extends
: Podemos criar relacionamentos hierárquicos entre classes usando a palavra-chave extends
. Isso permite que classes filhas herdem propriedades e métodos das classes pai.
Por exemplo:
class Funcionario extends Pessoa {
cargo: string;
constructor(nome: string, cargo: string) {
super(nome);
this.cargo = cargo;
}
}
let funcionario = new Funcionario("Maria", "Desenvolvedora");
funcionario.cumprimentar(); // Herdado de Pessoa
Implementa interfaces usando implements
: Interfaces em TS definem um contrato que as classes devem satisfazer. Elas descrevem a forma/estrutura ao invés da implementação. Podemos usar implements
para garantir que as classes adiram a uma interface particular.
Por exemplo:
interface Falavel {
falar(): void;
}
class Gato implements Falavel {
falar() {
console.log("Miau");
}
}
Por que herança única: TypeScript permite que uma classe estenda apenas uma classe por vez (herança única). Estender mais de uma classe pai leva a complexidades como o problema de diamante na herança múltipla.
Interfaces podem ser implementadas por classes de qualquer número e fornecem uma maneira mais flexível de definir contratos para que as classes satisfaçam.
Então, em resumo, extends
define um relacionamento é-um para herança, enquanto implements
impõe um contrato definido por interfaces.
Alias
Os aliases de type e as interfaces são muito semelhantes e, em muitos casos, você pode escolher entre eles livremente. Quase todos os recursos de uma interface estão disponíveis em tipo, a principal distinção é que um tipo não pode ser reaberto para adicionar novas propriedades em comparação com uma interface que é sempre extensível.
Interface | Type |
---|---|
|
|
|
|
Interfaces
Lembra que falamos que representar um objeto como object não é legal? É aí que as interfaces entram e nos ajudam (bastante). Exemplo:
interface EveryExampleInOne {
str: string;
num: number;
bool: boolean;
func(arg1: string): void;
arr: string[];
}
Onde temos uma interface EveryExampleInOne que possui 5 propriedades. Elas possuem, respectivamente, os seguintes tipos:
- string
- number
- boolean
- Função que recebe um argumento do tipo string e tem como retorno o tipo void
- Array de strings
Optional Properties
Uma possibilidade interessante nas interfaces é definir uma propriedade como opcional. Exemplo:
interface Dog {
name: string;
owner?: string;
}
Onde temos que o nome do cachorro é obrigatório, mas o nome do dono é opcional.
Dynamic Properties
Além disso, outro caso interessante é quando além das propriedades que declaramos, queremos deixar em aberto que novas propriedades de um certo tipo sejam sejam adicionadas. Exemplo:
interface User {
name: string;
email: string;
[propName: string]: string;
}
Onde temos uma interface User na qual, além das 2 propriedades que definimos, deixamos em aberto a possibilidade de N novas propriedades de nome (propName) string cujo valor também é do tipo string. Poderíamos implementar algo do tipo:
const doge: User = {
name: "Joseph Oliveira",
email: "doge@rocketseat.com.br",
nickname: "Dogim",
address: "Dogeland"
}
Readonly Properties
Além disso, podemos também definir que uma propriedade é apenas para leitura, pode atribuir um valor a ela apenas uma vez. Segue um exemplo:
interface Avengers {
readonly thanos: string;
}
let theEnd: Avengers = { thanos: "I'm inevitable" }
theEnd.thanos = "I'm not inevitable" // erro
Implements
Utilizando conceitos já comuns em linguagens tipadas como C# e Java, temos a possibilidade de reforçar que uma classe (ou uma função) atenda os critérios definidos em uma interface. Exemplo:
interface BalanceInterface {
increment(income: number): void;
decrement(outcome: number): void;
}
class Balance implements BalanceInterface {
private balance: number;
constructor() {
this.balance = 0;
}
increment(income: number): void {
this.balance += income;
}
decrement(outcome: number): void {
this.balance -= outcome;
}
}
Lembrando que ao utilizar o implements para que a interface force a classe a seguir os padrões impostos, só conseguimos referenciar o lado público (public) da classe.
Extends
Outro conceito importante já apresentado nessas linguagens é a possibilidade de uma interface herdar propriedades de outra interface. Exemplo:
interface Aircraft {
speed: number;
}
interface Fighter extends Aircraft {
hasMissiles: boolean;
missiles?: number;
}
const f22: Fighter = {
speed: 2000,
hasMissiles: true,
missiles: 4,
};
Union Types
Em alguns casos, queremos que uma variável/propriedade aceite mais de um tipo. Para esses casos, utilizamos os Union Types. Exemplo:
let age: number | string = 30;
age = "30";
age = false; // erro
Generics
Vimos diversas formas até agora de como realizar a tipagem com Typescript, até mesmo em casos mais complexos como funções e objetos. Mas e se, por exemplo, não soubermos, durante o desenvolvimento, qual tipo o argumento e o retorno de uma função devem receber? Para isso utilizamos os Generics. Exemplo:
const mibr: Array<string> = ["Fallen", "Fer", "Taco", "Kng", "Trk"];
Nesse simples exemplo utilizamos um generic do próprio Typescript, o Array, em que o tipo informado dentro de <> representa o tipo dos valores do array. É o equivalente de string[]. Agora vamos a um exemplo mais complexo:
function example<T>(arg: T): T {
return arg;
}
Nesse caso, declaramos uma função example que recebe um argumento do tipo T e retorna um valor do tipo T. Então:
const value = example<string>("rocketseat");
console.log(value) // irá printar o valor "rocketseat"
Type assertions
As vezes, você pode saber mais de um tipo do que o próprio Typescript, principalmente ao trabalho com tipos como any ou object. Por isso, é possível atribuir manualmente um tipo utilizando Type assertions. Exemplo:
const bestDog: any = "Doge";
const dogLength: number = (bestDog as string).length;
Onde atribuímos manualmente o tipo string a variável bestDog utilizando o as (anteriormente do tipo any).
Utility Types
Muitas vezes, em uma mesma aplicação acabamos gerando interfaces que possuem muitas semelhanças mas que não são necessariamente iguais. Isso, além de causar um código mais verboso, também é mais trabalhoso e suscetível a erros. Por isso, o Typescript disponibiliza os Utility Types. Eles vêm com a missão de evitar esses problemas e gerar rapidamente interfaces a partir de outras pre-existentes. Nessa seção iremos falar de dois exemplos apenas, mas fique a vontade para olhar o restante aqui.
Pick<T, K>
Utilizado quando queremos pegar apenas algumas propriedades (K) de uma outra interface (T). Exemplo:
interface Video {
title: string;
description: string;
fps: number;
duration: number;
}
type Image = Pick<Video, 'title' | 'description'>;
const picture: Image = {
title: 'Profile',
description: "Picture taken for my driver's license",
};
Omit<T, K>
Utilizando quando queremos excluir apenas algumas propriedades (K) de uma outra interface (T). Exemplo:
interface Video {
title: string;
description: string;
fps: number;
duration: number;
}
type Image = Omit<Video, 'fps' | 'duration'>;
const picture: Image = {
title: 'Profile',
description: "Picture taken for my driver's license",
};
Referências
Como essa é uma documentação básica e bem voltada a prática, é inviável tratar de todas as peculiaridades do Typescript. Por isso, deixaremos abaixo links que podem te ajudar a sanar eventuais dúvidas não tratadas aqui:
Handbook (Oficial)
Declaration Files (Oficial)
Project Configuration (Oficial)
typescript-cheatsheet
React Typescript Cheatsheet
React TypeScript Cheatsheets | React TypeScript Cheatsheets
Ferramentas
Convert JSON into types: https://quicktype.io/
Exercícios: https://typehero.dev/
É isso dev, esperamos que tenha gostado da doc e que entenda o poder que o Typescript pode adicionar ao Javascript. Só não vai botar any em tudo hein?