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
.tspara exportações e importações explícitas de tipos; reserve arquivos.d.tsestritamente para declarações ambientes como variáveis de ambiente ou aumentos globais. - Adote uma convenção de nomenclatura consistente, como
*.types.tspara tipos específicos de módulos e arquivos barrelindex.tspara caminhos de importação limpos e estáveis. - Em monorepos ou pacotes compartilhados, exponha tipos através da condição
typesnas exportações dopackage.jsone 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.tscompartilhado 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.
Discover how at OpenReplay.com.
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áveisimport.meta.envpara Vite ou bundlers similaresglobal.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ão | Exemplo | Caso de uso |
|---|---|---|
*.types.ts | user.types.ts | Tipos específicos de módulo |
index.ts | types/index.ts | Re-exportação barrel |
env.d.ts | env.d.ts | Declarações de variáveis de ambiente |
global.d.ts | global.d.ts | Aumento 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.