TypeScript: funções como parâmetros com tipagem estrita — análise completa com exemplos
\n\nOlá, colega! Se você já escreveu em JavaScript e passou uma função para outra função (por exemplo, em setTimeout ou Array.map), você já conhece o conceito de funções de ordem superior. Mas quando se trata de TypeScript, surge uma pergunta lógica: "É possível tipar estritamente a função que estou passando como parâmetro?". A resposta é sim, e este é um dos recursos mais poderosos da linguagem.
Neste artigo, vamos analisar como declarar tipos de funções em TypeScript, como passá-las como argumentos e como não errar com as assinaturas. Você aprenderá a tornar o código não apenas seguro, mas também autodocumentado. Vamos lá!
\n\nA propósito, esta é uma das perguntas mais populares no Stack Overflow (milhares de visualizações). Então, se você quer entender de uma vez por todas — está no lugar certo.
\n\nPor que isso é importante: a tipagem estrita economiza tempo
\n\nImagine que você está escrevendo uma função que recebe outra função (callback). Sem tipos, você pode acidentalmente passar uma função com o número errado de argumentos ou com o valor de retorno incorreto. O TypeScript detecta esses erros em tempo de compilação, não em tempo de execução. É como um seguro para o seu código.
\n\nAqui está um exemplo que não funcionará sem tipos:
\n\n// JavaScript (sem tipos) — o erro ocorrerá apenas em tempo de execução\nfunction processData(callback) {\n const result = callback(10, 20);\n console.log(result);\n}\n\n// Opa, passamos uma função com apenas um parâmetro\nprocessData((x) => x * 2); // NaN — porque o segundo parâmetro é undefined\n\nCom TypeScript, podemos especificar explicitamente qual assinatura o callback deve ter:
\n\n// TypeScript — erro em tempo de compilação\nfunction processData(callback: (a: number, b: number) => number): void {\n const result = callback(10, 20);\n console.log(result);\n}\n\n// ✅ Correto\nprocessData((x, y) => x + y); // 30\n\n// ❌ Erro: O tipo '(x: number) => number' não é atribuível ao tipo '(a: number, b: number) => number'\n// processData((x) => x * 2);\n\nLegal, né? Agora vamos analisar todas as formas de declarar esses tipos.
\n\nForma 1: Assinatura inline (a mais simples)
\n\nA forma mais direta é especificar a assinatura da função diretamente nos parâmetros. Isso é útil quando a função-parâmetro é usada apenas uma vez.
\n\n// Função que recebe outra função com dois números e retorna uma string\nfunction formatNumbers(\n a: number,\n b: number,\n formatter: (x: number, y: number) => string\n): string {\n return formatter(a, b);\n}\n\n// Uso\nconst result = formatNumbers(5, 10, (x, y) => `Soma: ${x + y}`);\nconsole.log(result); // "Soma: 15"\n\nVantagens: rápido, claro.
\nDesvantagens: se a assinatura se repetir, você terá que duplicar o código.
Forma 2: Type alias para função (reutilizável)
\n\nSe você usar a mesma assinatura em vários lugares, é melhor criar um type alias. Isso torna o código mais limpo e fácil de manter.
\n\n// Declarando o tipo da função\ntype Callback = (error: Error | null, data: string) => void;\n\n// Função que recebe um Callback\nfunction fetchData(url: string, callback: Callback): void {\n // Simulação de requisição assíncrona\n setTimeout(() => {\n if (url) {\n callback(null, "Dados recebidos");\n } else {\n callback(new Error("URL vazia"), "");\n }\n }, 1000);\n}\n\n// Uso\nfetchData("/api/data", (err, data) => {\n if (err) {\n console.error("Erro:", err.message);\n } else {\n console.log("Sucesso:", data);\n }\n});\n\nObserve que usamos void como tipo de retorno. Isso significa que a função pode não retornar