TypeScript

Funções em TypeScript: Assinaturas, Overloads e this Tipado na Prática

12 min de leitura

Funções em TypeScript: Assinaturas, Overloads e this Tipado na Prática

Fundamentos de Funções em TypeScript 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. 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. Olá, ${nome} ${sobrenome} Olá, ${nome} Observe que os tipos vêm após os parâmetros ( ) e o tipo de retorno vem após os parênteses ( ). Isso é sintaxe TypeScript pura. O compilador valida essas assinaturas antes mesmo de você executar o código. --- Assinaturas de Função: O

<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 = &quot;padrão&quot;): object {

return { tipo, criadoEm: new Date() };

}

// Função com rest parameters (aceita múltiplos argumentos)

function concatenar(...palavras: string[]): string {

return palavras.join(&quot; &quot;);

}

// Arrow function tipada

const multiplicar = (x: number, y: number): number =&gt; 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 &gt;= 18,

msg: dados.idade &gt;= 18 ? &quot;Maior de idade&quot; : &quot;Menor de idade&quot;

};

}

// 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 &gt;= 18,

msg: dados.idade &gt;= 18 ? &quot;Maior de idade&quot; : &quot;Menor de idade&quot;

};

}</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&lt;T&gt;(array: T[]): T | undefined {

return array[0];

}

// TypeScript infere o tipo automaticamente

const num = primeiro([1, 2, 3]); // num é number

const str = primeiro([&quot;a&quot;, &quot;b&quot;]); // str é string

// Genérico com restrição

function obterPropriedade&lt;T, K extends keyof T&gt;(obj: T, chave: K): T[K] {

return obj[chave];

}

const pessoa = { nome: &quot;Ana&quot;, idade: 28 };

const nome = obterPropriedade(pessoa, &quot;nome&quot;); // string

// obterPropriedade(pessoa, &quot;email&quot;); // 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>&lt;T&gt;</code> diz &quot;qualquer tipo&quot;, 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 === &quot;string&quot;) {

return valor.toUpperCase();

}

return valor * 2;

}

// O problema: o retorno é impreciso

const resultado = processar(&quot;olá&quot;); // 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 === &quot;string&quot;) {

return valor.toUpperCase();

}

return valor * 2;

}

// Agora TypeScript entende a relação exata

const resultado1 = processar(&quot;olá&quot;); // 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: &quot;GET&quot; | &quot;POST&quot;;

body?: unknown;

}

// Sobrecargas: GET retorna string, POST retorna object

function requisicao(config: { url: string; metodo: &quot;GET&quot; }): Promise&lt;string&gt;;

function requisicao(config: { url: string; metodo: &quot;POST&quot;; body: unknown }): Promise&lt;object&gt;;

function requisicao(config: ConfigAPI): Promise&lt;string | object&gt; {

// Implementação simulada

if (config.metodo === &quot;GET&quot;) {

return Promise.resolve(&quot;dados&quot;);

}

return Promise.resolve({ status: 200 });

}

// Uso tipado com segurança

requisicao({ url: &quot;/users&quot;, metodo: &quot;GET&quot; }).then(data =&gt; {

console.log(data); // string

});

requisicao({ url: &quot;/users&quot;, metodo: &quot;POST&quot;, body: { nome: &quot;João&quot; } }).then(data =&gt; {

console.log(data); // object

});</code></pre>

<p>O compilador valida que quando <code>metodo</code> é <code>&quot;POST&quot;</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&lt;T, R&gt;(valor: T, transformador: (v: T) =&gt; R): R {

return transformador(valor);

}

transformar(5, n =&gt; n * 2); // 10

transformar(&quot;oi&quot;, s =&gt; 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: &quot;João&quot;,

saudar: function() {

console.log(this.nome); // &#039;this&#039; 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;

}

// &#039;this&#039; é tipado como Usuario

function apresentar(this: Usuario): string {

return Sou ${this.nome} e tenho ${this.idade} anos;

}

const usuario: Usuario = { nome: &quot;Maria&quot;, idade: 30 };

// Precisa ser chamado com call, apply ou bind

console.log(apresentar.call(usuario)); // &quot;Sou Maria e tenho 30 anos&quot;

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 &#039;this&#039; vinculado

sacar = (valor: number): void =&gt; {

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 &#039;this&#039; automático

this.elemento.addEventListener(&quot;click&quot;, () =&gt; this.aoClicar());

}

// &#039;this&#039; é explicitamente tipado como Botao

aoClicar(this: Botao): void {

this.cliques++;

console.log(Cliques: ${this.cliques});

}

}

const botao = new Botao(&quot;meuBotao&quot;);</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 &quot;que funciona&quot; e código TypeScript &quot;que você entende e confia&quot;. 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&#039;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>&lt;!-- FIM --&gt;</p>

Comentários

Mais em TypeScript

TypeScript Compiler API: Parsear, Transformar e Gerar Código na Prática
TypeScript Compiler API: Parsear, Transformar e Gerar Código na Prática

Introdução à TypeScript Compiler API A TypeScript Compiler API é um conjunto...

Dominando Mixins em TypeScript: Composição de Comportamentos sem Herança em Projetos Reais
Dominando Mixins em TypeScript: Composição de Comportamentos sem Herança em Projetos Reais

O Problema da Herança Clássica Quando começamos a programar orientada a objet...

O que Todo Dev Deve Saber sobre Migração de JavaScript para TypeScript: Estratégias para Projetos Reais
O que Todo Dev Deve Saber sobre Migração de JavaScript para TypeScript: Estratégias para Projetos Reais

Entendendo o Contexto: Por Que Migrar para TypeScript? JavaScript é uma lingu...