Back

Como Organizar Definições de Tipos em um Projeto TypeScript

Como Organizar Definições de Tipos em um Projeto TypeScript

A maioria dos projetos TypeScript começa da mesma forma: um único arquivo types.ts que lentamente cresce até centenas de linhas. Encontrar qualquer coisa se torna uma tarefa árdua. Reutilizar tipos entre arquivos parece arriscado. Novos membros da equipe não sabem onde procurar.

A boa notícia é que organizar tipos TypeScript não requer um sistema complexo. Requer uma regra clara: coloque os tipos o mais próximo possível de onde são usados e só os mova quando forem necessários em outro lugar.

Veja como aplicar essa regra em cada etapa de um projeto.

Pontos-Chave

  • Coloque os tipos o mais próximo possível de seu uso e promova-os para locais compartilhados apenas quando múltiplos arquivos precisarem deles.
  • Use arquivos .ts para exportações e importações explícitas de tipos; reserve arquivos .d.ts estritamente para declarações ambientes como variáveis de ambiente ou aumentos globais.
  • Adote uma convenção de nomenclatura consistente, como *.types.ts para tipos específicos de módulos e arquivos barrel index.ts para caminhos de importação limpos e estáveis.
  • Em monorepos ou pacotes compartilhados, exponha tipos através da condição types nas exportações do package.json e mantenha os tipos internos privados.

A Decisão Central: Onde um Tipo Deve Ficar?

Faça a si mesmo uma pergunta: quantos arquivos precisam deste tipo?

  • Um arquivo → defina-o inline naquele arquivo
  • Alguns arquivos no mesmo módulo → coloque-o em um arquivo .types.ts compartilhado próximo
  • Em todo o projeto → mova-o para um diretório compartilhado src/types/
  • Entre pacotes → publique-o a partir de um pacote de tipos dedicado

Esta progressão mantém as definições de tipos do seu projeto organizadas sem excesso de engenharia desde o início.

Padrão 1: Tipos Inline para Uso Local

Se um tipo é usado apenas em um arquivo, defina-o ali. Nenhum arquivo separado é necessário.

// UserCard.tsx
interface UserCardProps {
  name: string
  avatarUrl: string
  role: "admin" | "viewer"
}

export function UserCard({ name, avatarUrl, role }: UserCardProps) {
  // ...
}

Este é o padrão correto. Abstração prematura cria indireção sem benefício.

Padrão 2: Arquivos de Tipos Colocados para Tipos Compartilhados de Módulo

Quando dois ou mais componentes na mesma pasta compartilham tipos, extraia-os para um arquivo .types.ts colocado.

src/components/user/
├── UserCard.tsx
├── UserList.tsx
└── user.types.ts       ← tipos compartilhados para este módulo

Isso mantém os tipos relacionados próximos ao código que os usa sem poluir um arquivo global.

Padrão 3: Um Diretório types/ Compartilhado para Definições Transversais

Quando os tipos são usados em múltiplos módulos não relacionados, um diretório central src/types/ faz sentido. Use um arquivo barrel index.ts para manter as importações limpas e estáveis.

src/types/
├── index.ts            ← re-exporta tudo
├── api.types.ts
├── user.types.ts
└── product.types.ts
// src/types/index.ts
export type { ApiResponse, PaginatedResult } from "./api.types"
export type { User, UserRole } from "./user.types"

Os consumidores importam de um único caminho: import type { User } from "@/types". Se você reorganizar os internos posteriormente, os caminhos de importação não mudam.

Você Deve Usar .ts ou .d.ts para Definições de Tipos?

Este é um dos pontos de confusão mais comuns na organização de tipos TypeScript.

Use arquivos .ts para tipos que você exporta e importa explicitamente. Esta é a escolha certa para quase tudo em uma aplicação.

Use arquivos .d.ts principalmente para declarações ambientes — situações em que você precisa informar ao TypeScript sobre algo que existe em tempo de execução mas não tem código-fonte TypeScript. Exemplos comuns:

  • env.d.ts — declarando variáveis import.meta.env para Vite ou bundlers similares
  • global.d.ts — aumentando tipos de módulos de terceiros ou declarando variáveis globais
// env.d.ts
/// <reference types="vite/client" />
interface ImportMetaEnv {
  readonly VITE_API_URL: string
}

Alguns projetos modernos configuram compilerOptions.types explicitamente para controlar quais tipos ambientes são carregados, enquanto outros dependem da descoberta automática de @types. Se você está usando uma toolchain baseada em bundler como Vite ou Next.js, siga suas convenções recomendadas para esses arquivos em vez de criar declarações globais independentes.

Convenções de Nomenclatura que Escalam

Nomenclatura consistente reduz a carga cognitiva em uma equipe:

PadrãoExemploCaso de uso
*.types.tsuser.types.tsTipos específicos de módulo
index.tstypes/index.tsRe-exportação barrel
env.d.tsenv.d.tsDeclarações de variáveis de ambiente
global.d.tsglobal.d.tsAumento de tipos de terceiros

Evite prefixos IUser e sufixos UserType. Nomes simples como User e ApiResponse são mais limpos e se alinham com as convenções modernas do TypeScript.

Organizando Tipos TypeScript em um Pacote Compartilhado

Se você está construindo uma biblioteca ou trabalhando em um monorepo, exponha tipos através das exportações do package.json usando a condição types em vez de depender de padrões typesVersions mais antigos.

{
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js"
    }
  }
}

Mantenha os tipos internos privados — não os exporte do ponto de entrada do pacote. Apenas a superfície da API pública deve ser visível para os consumidores.

Conclusão

Comece com tipos inline. Extraia para um arquivo colocado quando um segundo arquivo precisar deles. Promova para src/types/ quando forem verdadeiramente transversais. Use arquivos .d.ts apenas para declarações ambientes, não como um local padrão para todas as definições de tipos.

O objetivo de organizar tipos TypeScript não é uma estrutura de pastas perfeita — é fazer com que o próximo desenvolvedor (ou você no futuro) seja capaz de encontrar e reutilizar tipos sem fricção.

Perguntas Frequentes

Não. Um único arquivo funciona no início, mas se torna difícil de navegar à medida que um projeto cresce. Em vez disso, mantenha os tipos próximos de onde são usados. Defina-os inline para uso em arquivo único, extraia para um arquivo .types.ts colocado quando compartilhados dentro de um módulo, e só promova para um diretório de tipos central quando forem necessários em partes não relacionadas da base de código.

Use arquivos .ts para tipos que você exporta e importa explicitamente no código da sua aplicação. Use arquivos .d.ts apenas para declarações ambientes, como descrever variáveis de ambiente ou aumentar tipos de módulos de terceiros. Colocar tipos regulares de aplicação em arquivos .d.ts pode causar confusão porque essas declarações ficam disponíveis globalmente sem uma importação explícita.

Uma convenção amplamente adotada é usar o padrão nome-do-modulo.types.ts para tipos específicos de módulos e um arquivo barrel index.ts para re-exportações em um diretório de tipos compartilhado. Evite notação húngara como IUser ou sufixos redundantes como UserType. Nomes simples e descritivos como User e ApiResponse são preferidos em projetos TypeScript modernos.

Use a condição types dentro do campo exports do seu package.json para apontar para seu arquivo de declaração compilado. Esta abordagem é mais explícita e confiável do que o padrão typesVersions mais antigo. Exporte apenas tipos que formam parte da sua API pública e mantenha os tipos internos privados para evitar vazar detalhes de implementação para os consumidores.

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Check our GitHub repo and join the thousands of developers in our community.

OpenReplay