<h2>O que são Arquivos .d.ts e Por Que Importam</h2>
<p>Os arquivos <code>.d.ts</code> (TypeScript Declaration Files) são documentos especiais que descrevem a estrutura de tipos de código JavaScript. Quando você trabalha com uma biblioteca JavaScript sem suporte nativo a TypeScript, o arquivo <code>.d.ts</code> atua como um intermediário que fornece informações de tipo ao seu editor e ao compilador TypeScript, permitindo autocompletar, verificação de tipos e documentação contextual.</p>
<p>Imagine que você usa uma biblioteca JavaScript clássica em um projeto TypeScript. Sem tipos, o TypeScript a tratará como <code>any</code>, perdendo todos os benefícios de segurança de tipos. O arquivo <code>.d.ts</code> resolve isso descrevendo, em linguagem TypeScript, quais funções existem, quais parâmetros elas aceitam e o que retornam. É como fornecer um "contrato" que a biblioteca JavaScript cumpre, sem modificar o código JavaScript original.</p>
<h2>Estrutura Básica de um Arquivo .d.ts</h2>
<h3>Sintaxe Fundamental</h3>
<p>Um arquivo <code>.d.ts</code> usa a mesma sintaxe que arquivos <code>.ts</code> normais, mas contém apenas declarações de tipo, não implementação. Vamos começar com um exemplo simples. Suponha que você tenha uma biblioteca JavaScript chamada <code>math-helper.js</code>:</p>
<pre><code class="language-javascript">// math-helper.js (JavaScript puro)
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
module.exports = { add, multiply };</code></pre>
<p>O arquivo <code>.d.ts</code> correspondente seria:</p>
<pre><code class="language-typescript">// math-helper.d.ts
export function add(a: number, b: number): number;
export function multiply(a: number, b: number): number;</code></pre>
<p>A diferença crucial é que no <code>.d.ts</code> você <em>declara</em> tipos sem implementar a lógica. Use <code>export</code> para funções que serão acessíveis externamente, e sempre especifique tipos de parâmetros e retorno.</p>
<h3>Declarando Interfaces e Tipos</h3>
<p>Quando sua biblioteca trabalha com objetos complexos, você precisa descrever sua estrutura. Use <code>interface</code> para contratos e <code>type</code> para aliases. Aqui está um exemplo mais realista:</p>
<pre><code class="language-typescript">// user-service.d.ts
export interface User {
id: number;
name: string;
email: string;
isActive?: boolean; // propriedade opcional
}
export interface CreateUserRequest {
name: string;
email: string;
}
export function getUser(id: number): Promise<User>;
export function createUser(data: CreateUserRequest): Promise<User>;
export function deleteUser(id: number): Promise<void>;</code></pre>
<p>Note que a propriedade <code>isActive</code> tem <code>?</code>, indicando que é opcional. Este padrão permite que quem usar a biblioteca saiba exatamente qual estrutura esperar, sem ler a documentação manualmente.</p>
<h2>Tipando Padrões Comuns de Bibliotecas JavaScript</h2>
<h3>Classes e Construtores</h3>
<p>Muitas bibliotecas JavaScript expõem classes. No <code>.d.ts</code>, declare usando <code>declare class</code>:</p>
<pre><code class="language-typescript">// event-emitter.d.ts
declare class EventEmitter {
constructor();
on(eventName: string, callback: (data: any) => void): void;
emit(eventName: string, data?: any): void;
off(eventName: string, callback: (data: any) => void): void;
}
export = EventEmitter;</code></pre>
<p>Neste exemplo, usamos <code>export =</code> (sintaxe CommonJS) porque a biblioteca original usa <code>module.exports</code>. Se fosse ES6, seria <code>export default EventEmitter</code> ou <code>export { EventEmitter }</code>.</p>
<h3>Callbacks e Funções de Ordem Superior</h3>
<p>JavaScript frequentemente passa funções como argumentos. Descreva usando tipos de função:</p>
<pre><code class="language-typescript">// request-helper.d.ts
export type RequestCallback = (error: Error | null, data?: any) => void;
export function fetchData(
url: string,
options?: RequestOptions,
callback?: RequestCallback
): void;
export interface RequestOptions {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
headers?: Record<string, string>;
timeout?: number;
}</code></pre>
<p>O tipo <code>RequestCallback</code> descreve uma função que recebe um erro opcional e dados opcionais. Use <code>Record<string, string></code> para descrever objetos chave-valor quando a estrutura é dinâmica.</p>
<h3>Genéricos</h3>
<p>Se a biblioteca usa tipos genéricos (como contêineres que aceitam qualquer tipo), descreva usando <code><T></code>:</p>
<pre><code class="language-typescript">// storage.d.ts
export interface Storage<T> {
get(key: string): T | undefined;
set(key: string, value: T): void;
clear(): void;
}
export function createStorage<T>(initialData?: T[]): Storage<T>;
// Exemplo de uso em TypeScript:
// const numberStorage = createStorage<number>();
// numberStorage.set('count', 42);</code></pre>
<p>Genéricos permitem que a mesma declaração funcione com múltiplos tipos, mantendo segurança em tempo de compilação.</p>
<h2>Estrutura de Projeto e Boas Práticas</h2>
<h3>Organização de Arquivos</h3>
<p>Em projetos maiores, organize múltiplos <code>.d.ts</code> em subdiretórios para refletir a estrutura da biblioteca:</p>
<pre><code>minha-biblioteca/
├── package.json
├── index.js
├── types/
│ ├── index.d.ts
│ ├── utils.d.ts
│ ├── services/
│ │ ├── user-service.d.ts
│ │ └── payment-service.d.ts
│ └── models/
│ ├── user.d.ts
│ └── payment.d.ts</code></pre>
<p>No <code>package.json</code>, indique o caminho para os tipos:</p>
<pre><code class="language-json">{
"name": "minha-biblioteca",
"main": "index.js",
"types": "types/index.d.ts"
}</code></pre>
<p>O campo <code>"types"</code> diz ao TypeScript onde encontrar as declarações, melhorando a experiência do desenvolvedor que usa sua biblioteca.</p>
<h3>Reutilizando Declarações</h3>
<p>Quando uma declaração é usada em múltiplos arquivos <code>.d.ts</code>, importe-a:</p>
<pre><code class="language-typescript">// types/models/user.d.ts
export interface User {
id: number;
name: string;
email: string;
}
// types/services/user-service.d.ts
import { User } from '../models/user';
export function getUser(id: number): Promise<User>;
export function updateUser(id: number, updates: Partial<User>): Promise<User>;</code></pre>
<p>Use <code>Partial<User></code> para indicar que nem todas as propriedades precisam ser fornecidas na atualização. Isso torna o código mais flexível e mantém a reutilização.</p>
<h3>Documentação Inline</h3>
<p>JSDoc comentários em <code>.d.ts</code> aparecem no editor do desenvolvedor:</p>
<pre><code class="language-typescript">// calculator.d.ts
/**
- Soma dois números.
- @param a - O primeiro número
- @param b - O segundo número
- @returns A soma dos números
- @example
- const result = add(5, 3); // 8
*/
export function add(a: number, b: number): number;
/**
- Opções de configuração para o calculador.
- @property precision - Número de casas decimais (padrão: 2)
- @property locale - Localização para formatação (padrão: 'en-US')
*/
export interface CalculatorOptions {
precision?: number;
locale?: string;
}</code></pre>
<p>Ao passar o mouse sobre <code>add</code> no VS Code, o desenvolvedor verá toda essa documentação. É um investimento pequeno que melhora muito a experiência.</p>
<h2>Exemplo Completo: Tipando uma Biblioteca Real</h2>
<p>Vamos tipar uma biblioteca fictícia mas realista chamada <code>form-validator</code>. O arquivo JavaScript original:</p>
<pre><code class="language-javascript">// form-validator.js
class FormValidator {
constructor(rules = {}) {
this.rules = rules;
this.errors = {};
}
addRule(fieldName, rule, message) {
if (!this.rules[fieldName]) {
this.rules[fieldName] = [];
}
this.rules[fieldName].push({ rule, message });
}
validate(data) {
this.errors = {};
for (const field in this.rules) {
const value = data[field];
for (const ruleObj of this.rules[field]) {
if (!ruleObj.rule(value)) {
if (!this.errors[field]) {
this.errors[field] = [];
}
this.errors[field].push(ruleObj.message);
}
}
}
return Object.keys(this.errors).length === 0;
}
getErrors() {
return this.errors;
}
}
module.exports = FormValidator;</code></pre>
<p>Agora o arquivo <code>.d.ts</code>:</p>
<pre><code class="language-typescript">// form-validator.d.ts
/**
- Função que valida um valor.
- @param value - O valor a ser validado
- @returns true se válido, false caso contrário
*/
export type ValidatorRule = (value: any) => boolean;
/**
- Definição de uma regra de validação com mensagem de erro.
*/
export interface ValidationRule {
rule: ValidatorRule;
message: string;
}
/**
- Classe para validar dados de formulário com regras customizáveis.
*/
declare class FormValidator {
/**
- Conjunto de regras organizadas por campo.
*/
rules: Record<string, ValidationRule[]>;
/**
- Dicionário de erros encontrados na última validação.
*/
errors: Record<string, string[]>;
/**
- Cria uma nova instância do validador.
- @param rules - Objeto contendo regras pré-definidas (opcional)
*/
constructor(rules?: Record<string, ValidationRule[]>);
/**
- Adiciona uma regra de validação a um campo.
- @param fieldName - Nome do campo
- @param rule - Função validadora
- @param message - Mensagem de erro se a validação falhar
*/
addRule(fieldName: string, rule: ValidatorRule, message: string): void;
/**
- Valida um objeto de dados contra as regras definidas.
- @param data - Objeto contendo os dados a validar
- @returns true se todos os dados são válidos, false caso contrário
*/
validate(data: Record<string, any>): boolean;
/**
- Retorna os erros da última validação.
- @returns Dicionário de erros por campo
*/
getErrors(): Record<string, string[]>;
}
export = FormValidator;</code></pre>
<p>Com este <code>.d.ts</code>, um desenvolvedor TypeScript pode usar a biblioteca com segurança de tipos total:</p>
<pre><code class="language-typescript">import FormValidator from 'form-validator';
const validator = new FormValidator();
validator.addRule('email', (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value), 'Email inválido');
validator.addRule('age', (value) => value >= 18, 'Deve ter 18 anos ou mais');
const isValid = validator.validate({ email: 'user@example.com', age: 25 });
if (!isValid) {
console.log(validator.getErrors());
}</code></pre>
<p>O TypeScript agora sabe exatamente quais métodos existem, quais parâmetros esperam e o que retornam.</p>
<h2>Conclusão</h2>
<p>Aprendemos que arquivos <code>.d.ts</code> são ferramentas essenciais para trazer segurança de tipos a bibliotecas JavaScript existentes. Primeiro, compreendemos que eles funcionam como um "contrato" de tipos que descreve interfaces públicas sem alterar o código JavaScript original. Segundo, dominamos a sintaxe fundamental: interfaces, tipos, genéricos e como declarar funções, classes e callbacks. Terceiro, internalizamos que boas práticas — como documentação inline, organização em diretórios e reutilização de tipos — transformam um simples <code>.d.ts</code> em uma experiência excelente para quem usa a biblioteca.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html" target="_blank" rel="noopener noreferrer">TypeScript Handbook: Declaration Files</a></li>
<li><a href="https://www.typescriptlang.org/docs/handbook/declaration-files/by-example.html" target="_blank" rel="noopener noreferrer">Microsoft: Writing Declaration Files</a></li>
<li><a href="https://github.com/DefinitelyTyped/DefinitelyTyped" target="_blank" rel="noopener noreferrer">DefinitelyTyped Repository</a> - Repositório com tipos para milhares de bibliotecas JavaScript</li>
<li><a href="https://www.typescriptlang.org/docs/handbook/utility-types.html" target="_blank" rel="noopener noreferrer">TypeScript: Utility Types</a> - Referência de tipos utilitários como Partial, Record e Required</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Tools/JSDoc" target="_blank" rel="noopener noreferrer">Mozzila MDN: JSDoc</a> - Documentação sobre comentários JSDoc em arquivos de tipo</li>
</ul>
<p><!-- FIM --></p>