Como Tipar da Forma Correta com TypeScript
O TypeScript tem ganhado popularidade por oferecer recursos avançados, como tipagem estática, que proporcionam um desenvolvimento mais seguro e eficiente. Neste artigo, vamos aprender a tipar de forma correta, explorando seus recursos e fornecendo exemplos práticos para ilustrar como ele pode elevar a qualidade do seu código.
Acesse também: Melhores Práticas com HTML Semântico para um Código Limpo
Configuração do TypeScript
Antes de aprender a tipar, vamos criar e entender o arquivo de configuração do typescript, o arquivo tsconfig.json
. Este arquivo contém as opções de configuração que definem como o TypeScript compilará seu código.
1. target:
A opção target
define para qual versão do ECMAScript o TypeScript compilará. Escolher uma versão mais recente permite o uso de recursos modernos do JavaScript.
"compilerOptions": {
"target": "es6",
}
2. module:
A opção module
especifica o sistema de módulos a ser usado. Para projetos Node.js, commonjs
é uma escolha comum.
"compilerOptions": {
"module": "commonjs", // pode ser utilizado outros modulos como o 'esnext' para aplicações Next.JS
}
3. strict:
Ativar strict
é altamente recomendado, pois habilita várias opções de checagem de tipo rigorosas que tornam o código mais seguro.
"compilerOptions": {
"strict": true,
}
4. outDir e rootDir:
Essas opções determinam onde os arquivos compilados serão colocados (outDir
) e onde o TypeScript procurará seus arquivos (rootDir
).
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
}
5. esModuleInterop:
Para garantir uma interoperabilidade suave entre módulos CommonJS e ES6, você pode ativar esModuleInterop
.
"compilerOptions": {
"esModuleInterop": true,
}
6. strictNullChecks:
Essa opção aperta as regras em torno de valores null
e undefined
, ajudando a evitar erros comuns.
"compilerOptions": {
"strictNullChecks": true,
}
7. Configuração Completa do TypeScript:
Aqui está um exemplo da configuração completa do TypeScript (no arquivo tsconfig.json
) com várias opções configuradas que você pode usar no seu projeto:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"strict": true,
"outDir": "./dist",
"rootDir": "./src",
"esModuleInterop": true,
"strictNullChecks": true
}
}
Ao configurar essas opções, você está adaptando o ambiente para atender às especificidades do seu projeto. Essa configuração é um equilíbrio entre segurança de tipo e flexibilidade, garantindo que você tenha um ambiente de desenvolvimento poderoso e adaptado às suas necessidades.
Tipagem Estática em Ação
1. Tipos Básicos:
TypeScript oferece uma variedade de tipos básicos, incluindo number
, string
, boolean
, array
, tuple
, enum
e any
. Vamos ver alguns exemplos:
// Números
let idade: number = 25;
// Texto
let nome: string = "João";
// Booleano
let ativo: boolean = true;
// Array
let numeros: number[] = [1, 2, 3];
// Tupla
let coordenadas: [number, number] = [10, 20];
// Enum
enum DiaDaSemana {
Segunda,
Terca,
Quarta,
Quinta,
Sexta,
Sabado,
Domingo,
}
let hoje: DiaDaSemana = DiaDaSemana.Sexta;
// Qualquer tipo (evitar seu uso quando possível)
let variavelQualquer: any = "qualquer coisa";
2. Funções com Tipos:
Ao criar funções, você pode especificar os tipos dos parâmetros e do retorno.
function somar(a: number, b: number): number {
return a + b;
}
function saudacao(nome: string): string {
return `Olá, ${nome}!`;
}
Observe que a função somar está tipada para receber um ‘a‘ e um ‘b‘ que são números, e irá retornar um número também.
3. Interfaces e Tipos Personalizados:
Use interfaces para definir a forma de um objeto e tipos personalizados para criar combinações complexas de tipos.
// Interface
interface Usuario {
nome: string;
idade: number;
}
function mostrarUsuario(usuario: Usuario): void {
console.log(`Nome: ${usuario.nome}, Idade: ${usuario.idade}`);
}
// Tipo Personalizado
type Coordenada = [number, number];
function mostrarCoordenada(coordenada: Coordenada): void {
console.log(`Latitude: ${coordenada[0]}, Longitude: ${coordenada[1]}`);
}
Interfaces é a forma mais profissional de fazer tipagem quando uma função recebe vários parâmetros como argumento.
TypeScript Intermediário
1. Classes e Herança:
TypeScript permite uma programação orientada a objetos mais robusta com suporte a classes e herança.
class Animal {
constructor(public nome: string) {}
fazerBarulho(): void {
console.log("Algum barulho genérico");
}
}
class Cachorro extends Animal {
fazerBarulho(): void {
console.log("Au au!");
}
}
const meuCachorro = new Cachorro("Bolt");
meuCachorro.fazerBarulho(); // Saída: Au au!
2. Genéricos:
Genéricos <T>
oferecem uma maneira de escrever código flexível que pode funcionar com diferentes tipos de dados.
function obterElementoAleatorio<T>(lista: T[]): T {
const indiceAleatorio = Math.floor(Math.random() * lista.length);
return lista[indiceAleatorio];
}
const elementoAleatorio = obterElementoAleatorio(["Maçã", "Banana", "Laranja"]);
console.log(elementoAleatorio); // Saída: Pode ser Maçã, Banana ou Laranja
3. Promises e Async/Await:
TypeScript facilita o uso de promessas e async/await para lidar com operações assíncronas.
function aguardarTempo(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function exemploAsync(): Promise<void> {
console.log("Início da execução");
await aguardarTempo(2000);
console.log("Execução após 2 segundos");
}
exemploAsync();
Uma boa prática em Promises e Async/Await é tipar elas com uma interface, como no exemplo abaixo.
// Interface
interface Usuario {
ms: number;
}
function aguardarTempo({ ms }: Usuario): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
TypeScript Avançado
1. Decoradores:
Decoradores são uma característica avançada do TypeScript que permite modificar ou estender a funcionalidade de classes, métodos, propriedades e acessores. Eles são comumente usados em conjunto com bibliotecas como Angular.
function logarClasse(construtor: Function) {
console.log(`Registrando a classe: ${construtor}`);
}
@logarClasse
class ExemploDecorador {
// Conteúdo da classe...
}
Quando a classe ExemploDecorador
é definida e marcada com @logarClasse
, o TypeScript automaticamente chama a função logarClasse
com o construtor da classe como argumento. Portanto, o que quer que esteja dentro da função logarClasse
será executado quando a classe for definida.
2. Tipos Condicionais:
Tipos condicionais permitem criar tipos que dependem de uma condição. Isso é particularmente útil ao lidar com tipos de dados dinâmicos.
type ExcluirPropriedade<T, K extends keyof T> = {
[P in keyof T as P extends K ? never : P]: T[P];
};
interface Usuario {
id: number;
nome: string;
email: string;
}
type UsuarioSemId = ExcluirPropriedade<Usuario, 'id'>;
// Resultado: { nome: string; email: string }
ExcluirPropriedade<T, K extends keyof T>
: Aceita dois parâmetros genéricos:T
(um tipo) eK
(uma chave desse tipoT
).[P in keyof T as P extends K ? never : P]: T[P]
: Este é um tipo mapeado. Ele itera sobre todas as propriedadesP
do tipoT
. A parte mais complexa está na expressão condicionalP extends K ? never : P
. Isso significa que, seP
for uma subchave deK
(ou seja,P extends K
), então ela é mapeada paranever
(um tipo que significa ausência de valor), caso contrário, ela é mantida comoP
.
3. Integração com Webpack:
Integração do TypeScript com Webpack é crucial para projetos mais complexos. Configurar o Webpack para compilar arquivos TypeScript e lidar com módulos é uma habilidade essencial.
// Arquivo webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.ts',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
resolve: {
extensions: ['.ts', '.js'],
},
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
};
4. Ambientes de Testes com Jest:
Para garantir a qualidade do código, é essencial ter testes automatizados. Integrar o TypeScript com o Jest simplifica a escrita de testes e garante uma cobertura abrangente.
// Arquivo jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
5. Mixins e Herança Múltipla:
Embora o JavaScript não suporte herança múltipla tradicional, você pode implementar padrões de design de mixins para obter funcionalidades compartilhadas em várias classes.
class SalvarMixin {
salvar() {
console.log('Salvando...');
}
}
class ValidarMixin {
validar() {
console.log('Validando...');
}
}
interface Usuario extends SalvarMixin, ValidarMixin {
nome: string;
email: string;
}
Use o TypeScript Sempre em Seus Projetos
O TypeScript oferece uma experiência de desenvolvimento web mais robusta, fornecendo tipagem estática, interfaces, genéricos e mais. Esses recursos não apenas tornam o código mais seguro, mas também melhoram a eficiência e a legibilidade. Ao adotar o TypeScript, você estará capacitando sua equipe a escrever código mais confiável e escalável, contribuindo para o sucesso de projetos web modernos.