<h2>Fundamentos de Funções em TypeScript</h2>
<p>Uma função em TypeScript é bem mais que um bloco de código reutilizável. É um contrato entre você e o compilador sobre o que entra, o que sai e como se comporta. Diferentemente de JavaScript puro, TypeScript exige que você seja explícito sobre tipos de parâmetros e retorno, o que reduz bugs em tempo de execução e melhora a inteligência das ferramentas de desenvolvimento.</p>
<p>Toda função em TypeScript começa com uma declaração clara de tipos. Mesmo que você não declare explicitamente, o TypeScript infere os tipos com base no contexto. Isso não é opcional em um projeto profissional — é o alicerce que permite confiar no código.</p>
<pre><code class="language-typescript">// Declaração básica com tipos explícitos
function somar(a: number, b: number): number {
return a + b;
}
// Função com parâmetros opcionais
function saudacao(nome: string, sobrenome?: string): string {
return sobrenome ? Olá, ${nome} ${sobrenome} : Olá, ${nome};
}
// Função com valor padrão
function criar(tipo: string = "padrão"): object {
return { tipo, criadoEm: new Date() };
}
// Função com rest parameters (aceita múltiplos argumentos)
function concatenar(...palavras: string[]): string {
return palavras.join(" ");
}
// Arrow function tipada
const multiplicar = (x: number, y: number): number => x * y;</code></pre>
<p>Observe que os tipos vêm após os parâmetros (<code>:tipo</code>) e o tipo de retorno vem após os parênteses (<code>): tipo</code>). Isso é sintaxe TypeScript pura. O compilador valida essas assinaturas antes mesmo de você executar o código.</p>
<p>---</p>
<h2>Assinaturas de Função: O Contrato Explícito</h2>
<p>Uma assinatura de função é a definição do seu contrato — quantos parâmetros, quais tipos, qual o retorno. Em TypeScript, você pode declarar assinaturas separadamente da implementação, o que é especialmente poderoso em cenários de complexidade.</p>
<h3>Assinaturas Simples e Compostas</h3>
<p>A forma mais simples é declarar a função e deixar o TypeScript inferir tudo. Mas isso não é profissional quando você trabalha em equipes. O ideal é ser explícito:</p>
<pre><code class="language-typescript">// Assinatura com tipos aninhados
function processar(dados: { nome: string; idade: number }): { sucesso: boolean; msg: string } {
return {
sucesso: dados.idade >= 18,
msg: dados.idade >= 18 ? "Maior de idade" : "Menor de idade"
};
}
// Usando tipos nomeados (mais limpo)
interface Usuario {
nome: string;
idade: number;
}
interface Resultado {
sucesso: boolean;
msg: string;
}
function processarUsuario(dados: Usuario): Resultado {
return {
sucesso: dados.idade >= 18,
msg: dados.idade >= 18 ? "Maior de idade" : "Menor de idade"
};
}</code></pre>
<p>A segunda abordagem é claramente superior. Interfaces e tipos nomeados tornam o código legível e reutilizável. Quando alguém ler <code>processarUsuario</code>, imediatamente sabe exatamente o que entra e o que sai.</p>
<h3>Assinaturas com Genéricos</h3>
<p>Genéricos são a forma TypeScript de escrever funções flexíveis mantendo segurança de tipo. Ao invés de aceitar <code>any</code> (o inimigo da segurança), você deixa o tipo ser descoberto dinamicamente:</p>
<pre><code class="language-typescript">// Função genérica que retorna o mesmo tipo que recebe
function primeiro<T>(array: T[]): T | undefined {
return array[0];
}
// TypeScript infere o tipo automaticamente
const num = primeiro([1, 2, 3]); // num é number
const str = primeiro(["a", "b"]); // str é string
// Genérico com restrição
function obterPropriedade<T, K extends keyof T>(obj: T, chave: K): T[K] {
return obj[chave];
}
const pessoa = { nome: "Ana", idade: 28 };
const nome = obterPropriedade(pessoa, "nome"); // string
// obterPropriedade(pessoa, "email"); // ERRO: email não existe em pessoa</code></pre>
<p>Essa é a verdadeira força do TypeScript: segurança de tipo sem sacrificar flexibilidade. O genérico <code><T></code> diz "qualquer tipo", mas mantém a coerência dentro da função.</p>
<p>---</p>
<h2>Overloads: Múltiplas Assinaturas, Uma Implementação</h2>
<p>Overload em TypeScript permite que uma função tenha múltiplas assinaturas válidas. Não é sobrecarga como em Java ou C++ — é um recurso de compilação que oferece melhor experiência ao desenvolvedor e segurança de tipo mais rigorosa.</p>
<h3>Casos de Uso Reais</h3>
<p>Imagine uma função que aceita tanto strings quanto números, mas se comporta diferentemente com cada tipo. Sem overload, você teria que usar <code>any</code> ou <code>string | number</code>, perdendo a segurança de tipo:</p>
<pre><code class="language-typescript">// SEM overload (ruim)
function processar(valor: string | number): string | number {
if (typeof valor === "string") {
return valor.toUpperCase();
}
return valor * 2;
}
// O problema: o retorno é impreciso
const resultado = processar("olá"); // TypeScript não sabe se é string</code></pre>
<p>Agora com overload:</p>
<pre><code class="language-typescript">// COM overload (correto)
function processar(valor: string): string;
function processar(valor: number): number;
function processar(valor: string | number): string | number {
if (typeof valor === "string") {
return valor.toUpperCase();
}
return valor * 2;
}
// Agora TypeScript entende a relação exata
const resultado1 = processar("olá"); // string
const resultado2 = processar(5); // number</code></pre>
<p>As duas primeiras linhas são assinaturas (sem implementação). A terceira é a implementação real, que precisa ser compatível com todas as assinaturas. Quando você chama a função, TypeScript usa a assinatura mais apropriada.</p>
<h3>Overload com Interfaces e Tipos Complexos</h3>
<p>Overloads brilham quando combinados com tipos complexos:</p>
<pre><code class="language-typescript">interface ConfigAPI {
url: string;
metodo: "GET" | "POST";
body?: unknown;
}
// Sobrecargas: GET retorna string, POST retorna object
function requisicao(config: { url: string; metodo: "GET" }): Promise<string>;
function requisicao(config: { url: string; metodo: "POST"; body: unknown }): Promise<object>;
function requisicao(config: ConfigAPI): Promise<string | object> {
// Implementação simulada
if (config.metodo === "GET") {
return Promise.resolve("dados");
}
return Promise.resolve({ status: 200 });
}
// Uso tipado com segurança
requisicao({ url: "/users", metodo: "GET" }).then(data => {
console.log(data); // string
});
requisicao({ url: "/users", metodo: "POST", body: { nome: "João" } }).then(data => {
console.log(data); // object
});</code></pre>
<p>O compilador valida que quando <code>metodo</code> é <code>"POST"</code>, <code>body</code> é obrigatório. Sem overload, seria impossível expressar essa relação.</p>
<h3>Limitações de Overload</h3>
<p>Overload não resolve tudo. Ele é bom para funções utilitárias, mas não substitui boas decisões arquiteturais. Se você precisa de mais de 3-4 overloads, seu design pode estar ruim. Nesses casos, considere usar genéricos ou padrões como factory functions.</p>
<pre><code class="language-typescript">// Genérico é melhor aqui que múltiplos overloads
function transformar<T, R>(valor: T, transformador: (v: T) => R): R {
return transformador(valor);
}
transformar(5, n => n * 2); // 10
transformar("oi", s => s.length); // 2</code></pre>
<p>---</p>
<h2><code>this</code> Tipado: Controlando o Contexto de Execução</h2>
<p><code>this</code> é famoso por ser confuso em JavaScript. TypeScript oferece uma forma elegante de controlar seu tipo e evitar erros em tempo de execução.</p>
<h3>O Problema com <code>this</code> não Tipado</h3>
<p>Em JavaScript, <code>this</code> depende de como a função é chamada, não de onde é definida. Isso causa bugs:</p>
<pre><code class="language-typescript">// Sem tipagem explícita (problema)
const obj = {
nome: "João",
saudar: function() {
console.log(this.nome); // 'this' pode ser undefined ou outra coisa
}
};
obj.saudar(); // funciona
const fn = obj.saudar;
fn(); // ERRO: this é undefined ou window</code></pre>
<h3>Solução: Parâmetro <code>this</code> Explícito</h3>
<p>TypeScript permite declarar <code>this</code> como primeiro parâmetro (não contado na chamada):</p>
<pre><code class="language-typescript">interface Usuario {
nome: string;
idade: number;
}
// 'this' é tipado como Usuario
function apresentar(this: Usuario): string {
return Sou ${this.nome} e tenho ${this.idade} anos;
}
const usuario: Usuario = { nome: "Maria", idade: 30 };
// Precisa ser chamado com call, apply ou bind
console.log(apresentar.call(usuario)); // "Sou Maria e tenho 30 anos"
console.log(apresentar.apply(usuario)); // mesmo resultado
// Ou com bind
const apresentarMaria = apresentar.bind(usuario);
console.log(apresentarMaria()); // funciona sem argumentos</code></pre>
<p>TypeScript agora valida que <code>this</code> é do tipo <code>Usuario</code>. Se você tentar chamar sem o contexto correto, há erro em tempo de compilação.</p>
<h3>Classes e Métodos com <code>this</code> Tipado</h3>
<p>Em classes, <code>this</code> é implicitamente tipado, mas você pode ser explícito para maior controle:</p>
<pre><code class="language-typescript">class Conta {
saldo: number = 1000;
depositar(this: Conta, valor: number): void {
this.saldo += valor;
console.log(Novo saldo: ${this.saldo});
}
// Método arrow não precisa, pois já tem 'this' vinculado
sacar = (valor: number): void => {
this.saldo -= valor;
console.log(Novo saldo: ${this.saldo});
};
}
const conta = new Conta();
conta.depositar(500); // OK
conta.sacar(200); // OK
// Com método regular, isso causaria erro
const dep = conta.depositar;
// dep(100); // ERRO: this não está vinculado</code></pre>
<h3>Caso Avançado: Validação de <code>this</code> em Callbacks</h3>
<p>Uma aplicação real e poderosa é em callbacks de eventos ou observadores:</p>
<pre><code class="language-typescript">class Botao {
elemento: HTMLButtonElement;
cliques: number = 0;
constructor(id: string) {
this.elemento = document.getElementById(id) as HTMLButtonElement;
// Usa arrow function para manter 'this' automático
this.elemento.addEventListener("click", () => this.aoClicar());
}
// 'this' é explicitamente tipado como Botao
aoClicar(this: Botao): void {
this.cliques++;
console.log(Cliques: ${this.cliques});
}
}
const botao = new Botao("meuBotao");</code></pre>
<p>A palavra-chave <code>this: Botao</code> força o compilador a validar que o método é sempre chamado com a instância correta como contexto.</p>
<p>---</p>
<h2>Conclusão</h2>
<p>Dominando funções em TypeScript, você controla três dimensões essenciais do código: <strong>assinaturas precisas</strong> que documentam intenção, <strong>overloads</strong> que permitem múltiplas formas de uso sem sacrificar segurança, e <strong><code>this</code> tipado</strong> que elimina erros sutis de contexto. Esses conceitos são a diferença entre código JavaScript "que funciona" e código TypeScript "que você entende e confia". Aplique-os consistentemente em seus projetos e verá imediatamente a redução de bugs em produção.</p>
<p>---</p>
<h2>Referências</h2>
<ul>
<li><a href="https://www.typescriptlang.org/docs/handbook/2/functions.html" target="_blank" rel="noopener noreferrer">TypeScript Official Documentation - Functions</a></li>
<li><a href="https://www.typescriptlang.org/docs/handbook/advanced-types.html" target="_blank" rel="noopener noreferrer">TypeScript Handbook - Advanced Types</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions" target="_blank" rel="noopener noreferrer">MDN Web Docs - Function Declarations</a></li>
<li><a href="https://effectivetypescript.com/" target="_blank" rel="noopener noreferrer">Effective TypeScript by Dan Vanderkam - O'Reilly</a></li>
<li><a href="https://basarat.gitbook.io/typescript/" target="_blank" rel="noopener noreferrer">TypeScript Deep Dive - Basarat Ali Syed</a></li>
</ul>
<p><!-- FIM --></p>