Abílio Azevedo.

Typescript

Cover Image for Typescript
Abílio Azevedo
Abílio Azevedo

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(&quot;João&quot;);
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(&quot;Maria&quot;, &quot;Desenvolvedora&quot;);
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(&quot;Miau&quot;); 
  }
}

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

interface Animal {
  name: string;
}

interface Bear extends Animal { honey: boolean; }

const bear = getBear(); bear.name; bear.honey;


type Animal = {
  name: string;
}

type Bear = Animal & { honey: boolean; }

const bear = getBear(); bear.name; bear.honey;


interface Window {
  title: string;
}

interface Window { ts: TypeScriptAPI; }

const src = 'const a = "Hello World"'; window.ts.transpileModule(src, {});


type Window = {
  title: string;
}

type Window = { ts: TypeScriptAPI; }

// Error: Duplicate identifier 'Window'.

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:

  1. string
  2. number
  3. boolean
  4. Função que recebe um argumento do tipo string e tem como retorno o tipo void
  5. 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)

A note about let #

Declaration Files (Oficial)

Sections #

Project Configuration (Oficial)

tsconfig.json

typescript-cheatsheet

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?


Mais posts

Cover Image for A psicologia do Dinheiro

A psicologia do Dinheiro

Morgan Housel oferece insights valiosos sobre a gestão financeira e tomada de decisões. O autor enfatiza que o sucesso financeiro depende mais do comportamento do que da inteligência ou conhecimento técnico. Housel destaca a importância da visão de longo prazo e da resiliência diante da volatilidade do mercado, encorajando a forcamos na sustentabilidade em vez de ganhos de curto prazo.

Cover Image for Bellsant

Bellsant

Estou na vanguarda do desenvolvimento de um aplicativo de saúde e bem-estar de ponta. Nossa pilha de tecnologia combina React Native para desenvolvimento móvel multiplataforma com um backend NodeJS sem servidor, aproveitando o AWS Lambda para escalabilidade e eficiência de custos.

Abílio Azevedo
Abílio Azevedo

NewsLetter

Eu enviarei o conteúdo postado aqui no blog. Sem Spam =)

Engenheiro de software experiente, formado em Engenharia Elétrica, com mais de 10 anos de experiência prática na construção de aplicativos móveis, web e back-end robustos e escaláveis em vários projetos, principalmente no setor de fintech. Mobile (React Native), Web (React e Next.JS) e Backend (Node.JS, PHP e DJANGO). Meu objetivo é criar produtos que agreguem valor às pessoas. - © 2024, Abílio Azevedo