Funções tipadas: parâmetros, retorno e overloads
1. Fundamentos da tipagem em funções
A tipagem em funções no TypeScript começa com a anotação explícita de tipos nos parâmetros e no valor de retorno. A sintaxe básica é direta:
function somar(a: number, b: number): number {
return a + b;
}
O TypeScript também é capaz de inferir o tipo de retorno automaticamente quando o corpo da função é simples o suficiente:
function multiplicar(a: number, b: number) {
return a * b; // TypeScript infere que o retorno é number
}
Para funções que não retornam valor, utilizamos o tipo void. É importante notar a diferença entre void e undefined:
function logMensagem(mensagem: string): void {
console.log(mensagem);
// Não há return, ou return sem valor
}
function retornaUndefined(): undefined {
return undefined; // Obrigatório retornar explicitamente
}
Enquanto void indica que a função não deve ter retorno útil, undefined exige que o valor undefined seja explicitamente retornado.
2. Parâmetros opcionais e com valores padrão
Parâmetros opcionais são declarados com ? após o nome. Uma regra importante: parâmetros opcionais devem vir após os obrigatórios:
function criarUsuario(nome: string, idade?: number): string {
if (idade !== undefined) {
return `Usuário: ${nome}, Idade: ${idade}`;
}
return `Usuário: ${nome}`;
}
console.log(criarUsuario("Ana")); // Usuário: Ana
console.log(criarUsuario("João", 30)); // Usuário: João, Idade: 30
Parâmetros com valores padrão são mais flexíveis e o TypeScript infere o tipo automaticamente:
function configurarTimeout(ms: number = 1000): void {
setTimeout(() => console.log(`Executando após ${ms}ms`), ms);
}
configurarTimeout(); // Usa 1000ms
configurarTimeout(3000); // Usa 3000ms
Uma diferença sutil: parâmetro?: tipo permite que o valor seja undefined ou omitido, enquanto parâmetro: tipo = valor define um fallback automático.
3. Parâmetros rest (...args) tipados
Para funções que aceitam quantidade variável de argumentos, usamos o operador rest com tipagem de array:
function somarTodos(...numeros: number[]): number {
return numeros.reduce((acc, curr) => acc + curr, 0);
}
console.log(somarTodos(1, 2, 3, 4)); // 10
Podemos combinar parâmetros fixos com rest:
function criarFrase(prefixo: string, ...palavras: string[]): string {
return `${prefixo}: ${palavras.join(" ")}`;
}
console.log(criarFrase("Frase", "TypeScript", "é", "poderoso"));
Para tipos heterogêneos, utilizamos tuplas:
function misturar(...args: [string, number, boolean]): void {
const [texto, numero, bool] = args;
console.log(texto, numero, bool);
}
misturar("teste", 42, true);
4. Tipos de função e callbacks
Podemos declarar tipos de função como variáveis ou parâmetros. A sintaxe usa seta (=>) para separar parâmetros do retorno:
type OperacaoMatematica = (a: number, b: number) => number;
const adicao: OperacaoMatematica = (x, y) => x + y;
const subtracao: OperacaoMatematica = (x, y) => x - y;
Type aliases são excelentes para reutilizar assinaturas complexas. Para callbacks, a tipagem garante segurança:
function processarArray(
itens: number[],
callback: (item: number, indice: number) => void
): void {
itens.forEach((item, indice) => callback(item, indice));
}
processarArray([1, 2, 3], (item, indice) => {
console.log(`Índice ${indice}: ${item}`);
});
5. Sobrecarga de funções (function overloads)
Sobrecarga permite que uma função tenha múltiplas assinaturas. Declaramos várias assinaturas e uma implementação única:
function processar(valor: string): string[];
function processar(valor: number): number;
function processar(valor: string | number): string[] | number {
if (typeof valor === "string") {
return valor.split("");
}
return valor * 2;
}
console.log(processar("abc")); // ["a", "b", "c"]
console.log(processar(5)); // 10
Casos comuns incluem funções que aceitam diferentes tipos de parâmetros e retornam tipos correspondentes:
function obterDados(id: number): { id: number; nome: string };
function obterDados(ids: number[]): { id: number; nome: string }[];
function obterDados(entrada: number | number[]): any {
if (Array.isArray(entrada)) {
return entrada.map(id => ({ id, nome: `Usuário ${id}` }));
}
return { id: entrada, nome: `Usuário ${entrada}` };
}
6. Funções genéricas básicas (introdução)
Genéricos permitem que funções trabalhem com tipos variados mantendo a relação entre parâmetro e retorno:
function primeiroElemento<T>(array: T[]): T | undefined {
return array[0];
}
const numero = primeiroElemento([1, 2, 3]); // tipo inferido como number
const texto = primeiroElemento(["a", "b"]); // tipo inferido como string
Podemos restringir genéricos com extends para maior segurança:
function obterPropriedade<T, K extends keyof T>(obj: T, chave: K): T[K] {
return obj[chave];
}
const pessoa = { nome: "Maria", idade: 25 };
console.log(obterPropriedade(pessoa, "nome")); // Maria
// console.log(obterPropriedade(pessoa, "altura")); // Erro: 'altura' não existe
Genéricos são ideais quando o tipo de retorno depende do tipo de entrada.
7. Boas práticas e armadilhas comuns
Evite any sempre que possível. Prefira tipos específicos ou genéricos:
// Ruim
function identidade(value: any): any {
return value;
}
// Bom
function identidade<T>(value: T): T {
return value;
}
Cuidado com overloads excessivas. Se os tipos de parâmetros são muito variados, considere usar união de tipos:
// Em vez de várias overloads:
function formatar(data: Date): string;
function formatar(data: string): string;
function formatar(data: number): string;
// Considere:
function formatar(data: Date | string | number): string {
// implementação única
}
Parâmetro this tipado é útil em funções que são usadas como métodos:
type ObjetoComNome = { nome: string };
function saudacao(this: ObjetoComNome): string {
return `Olá, ${this.nome}!`;
}
const obj = { nome: "Carlos", saudacao };
console.log(obj.saudacao()); // Olá, Carlos!
O TypeScript verifica se o this está sendo usado no contexto correto, prevenindo erros comuns.
Referências
- TypeScript Handbook: Functions — Documentação oficial sobre funções tipadas, incluindo parâmetros, retorno e overloads.
- TypeScript: Function Overloads — Seção específica sobre sobrecarga de funções na documentação oficial.
- TypeScript: Generics — Guia completo sobre funções genéricas e restrições com extends.
- TypeScript Deep Dive: Functions — Tutorial avançado sobre funções em TypeScript, com exemplos práticos de callbacks e tipos de função.
- [TypeScript: Callback Types and Best Practices](https://www.typescriptlang.org/play/#code/PTAEHkFcEsEsAcBcCmBPAZqFA9gOwBc4BLAOwHMAXKAUwCcBPAZToAc4BzOAYzQGNIAvKAA+oANoBdAJRIA3KAC+iRq3Zc+A4aIlSZ8hUpVqNmrDjyESZclVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR4qTPlLla8lVr0mAXT3DR
Evite overloads quando a lógica de implementação for a mesma. Overloads são úteis quando você precisa de diferentes assinaturas que se comportam de maneiras distintas, mas se a implementação é idêntica, uma união de tipos ou genéricos é mais adequada.
Prefira tipos específicos a any em callbacks:
// Ruim
function executar(fn: (dados: any) => void) {}
// Bom
function executar<T>(fn: (dados: T) => void) {}
Documente overloads complexas. Quando usar múltiplas overloads, explique o propósito de cada uma:
/**
* Busca um usuário por ID (retorna um único objeto)
* ou por array de IDs (retorna array de objetos)
*/
function buscarUsuario(id: number): Usuario;
function buscarUsuario(ids: number[]): Usuario[];
function buscarUsuario(entrada: number | number[]): Usuario | Usuario[] {
// implementação
}
Conclusão
Funções tipadas em TypeScript oferecem um sistema poderoso para garantir segurança de tipos em todo o fluxo de dados da aplicação. Desde a simples anotação de parâmetros e retorno até overloads e genéricos, cada recurso tem seu lugar específico:
- Use parâmetros opcionais e valores padrão para flexibilidade controlada
- Recorra a parâmetros rest para funções com número variável de argumentos
- Defina tipos de função para reutilizar assinaturas e tipar callbacks
- Aplique overloads quando diferentes combinações de parâmetros exigem retornos específicos
- Utilize genéricos para criar funções reutilizáveis que preservam a relação entre tipos
O equilíbrio entre expressividade e segurança é a chave: evite any, prefira tipos específicos ou genéricos, e não crie overloads desnecessárias quando uniões de tipos resolvem o problema.