TypeScript: Melhores práticas do TypeScript
Última atualização: 2026-08-26
Dominar a gramática é apenas o começo — para escrever um bom código em TypeScript, é preciso seguir um conjunto de práticas recomendadas. Esta lição resume as ideias e os princípios mais valiosos extraídos de projetos reais.
1. Convenções de nomenclatura
(1) Nomeação de tipos
| Categoria | Especificação | Exemplo |
|---|---|---|
| Interface | PascalCase | UserService, ApiResponse |
| Alias de tipo | PascalCase | Status, EventHandler |
| Parâmetro genérico | Letra única ou PascalCase | T, K, TItem |
| Enumeração | PascalCase (os valores podem estar em maiúsculas) | HttpStatus, COLOR_RED |
| Elementos da enumeração | PascalCase | HttpStatus.Ok |
(2) Nomeação de arquivos
| Tipo | Especificação | Exemplo |
|---|---|---|
| Módulo regular | camelCase | userService.ts |
| Tipo de arquivo | PascalCase | UserController.ts |
| Arquivo de declaração | Mesmo nome do módulo | lodash.d.ts |
| Arquivo de teste | Nome do módulo.test | userService.test.ts |
(3) Especificações de exportação
// ✅ Recommendations——Prioritize naming and exporting
export function addUser(user: User): void { }
export class UserController { }
export type Status = "active" | "inactive";
// ⚠️ Use Default Export with Caution——It's easy to make mistakes when renaming files
export default class UserController { }
// ✅ Library/Framework recommended — both options are available
export class UserController { }
export default UserController;
2. Princípios do design tipográfico
(1) Princípio 1: Precisão em vez de abrangência
// ❌ Broad——Type information lost
function process(value: any): any { }
// ❌ Slightly better, but still too broad
function process(value: string | number): string | number { }
// ✅ Accurate——Generics Preserve Type Information
function process<T extends string | number>(value: T): T { }
(2) Princípio 2: É melhor fazer cálculos do que escrever à mão
// ❌ Manually maintain two locations——Prone to desynchronization
interface User { id: number; name: string; email: string; }
type UserKeys = "id" | "name" | "email";
// ✅ Inference Based on Source Type——Automatic Synchronization
type UserKeys2 = keyof User; // "id" | "name" | "email"
type UserValues = User[keyof User]; // number | string
(3) Princípio 3: Composição em vez de herança
// ❌ Deep Inheritance——The Problem with Fragile Base Classes
class BaseEntity { id: number; }
class TimestampedEntity extends BaseEntity { createdAt: Date; }
class FullEntity extends TimestampedEntity { createdBy: string; }
// ✅ Type Combinations——Flexible and decoupled
type WithId = { id: number };
type WithTimestamps = { createdAt: Date; updatedAt: Date };
type WithAudit = { createdBy: string; updatedBy: string };
type FullEntity2 = WithId & WithTimestamps & WithAudit;
type SimpleEntity = WithId; // Customizable Combinations
▶ Exemplo: Refatoração de “any” para garantir segurança de tipos
// ❌ Before the refactoring——everywhere any,Zero Type Safety
function processRequest(req: any): any {
let user = req.body.user; // any
let result = validate(user); // any
return { status: 200, data: result };
}
// ✅ After the refactoring——Type safety at every step
interface User { id: number; name: string; email: string; }
interface Request2 { body: { user: User } }
interface ValidationResult { valid: boolean; errors?: string[] }
interface Response2<T> { status: number; data: T }
function processRequest2(req: Request2): Response2<ValidationResult> {
let user: User = req.body.user; // ✅ User Type
let result: ValidationResult = validate2(user); // ✅ ValidationResult
return { status: 200, data: result };
}
function validate2(user: User): ValidationResult {
if (!user.email.includes("@")) {
return { valid: false, errors: ["Invalid email address"] };
}
return { valid: true };
}
Saída:
// Executed successfully
3. Alternativas ao any
(1) desconhecido como alternativa a qualquer
// ❌ any——Turn off type checking
function process(value: any) {
return value.toUpperCase(); // Do not check,May crash during runtime
}
// ✅ unknown——You must narrow it first before you can use it.
function process2(value: unknown) {
if (typeof value === "string") {
return value.toUpperCase(); // ✅ Safe Use After Narrowing
}
throw new Error("Expectations string Type");
}
(2) Genéricos como substituto para any
// ❌ any——Missing Type Information
function first(arr: any[]): any {
return arr[0];
}
// ✅ Generics——Preserve Type Information
function first2<T>(arr: T[]): T {
return arr[0];
}
(3) Usando tipos de união em vez de any
// ❌ any
let value: any;
// ✅ Composite Types——Clearly list the possible types
let value2: string | number | boolean;
(4) Substituição de objetos any por assinaturas de índice
// ❌ any Object
let config: any = { host: "localhost" };
// ✅ Index Signature
let config2: Record<string, string | number> = { host: "localhost" };
4. Princípio DRY (Não se repita)
(1) Use keyof, typeof e tipos de utilitários para evitar duplicação
const THEMES = {
light: { bg: "#fff", text: "#333" },
dark: { bg: "#1a1a1a", text: "#e0e0e0" }
} as const;
// Type Inference from Values——No duplicate definitions
type ThemeName = keyof typeof THEMES; // "light" | "dark"
type ThemeColors = typeof THEMES["light"]; // { readonly bg: "..."; readonly text: "..." }
function getTheme(name: ThemeName): ThemeColors {
return THEMES[name];
}
(2) Transformações em lote utilizando tipos de mapeamento
interface ApiUser {
id: number;
name: string;
email: string;
role: string;
}
// No handwriting required——Derivation Using Tool Types
type CreateUserDTO = Omit<ApiUser, "id">;
type UpdateUserDTO = Partial<Omit<ApiUser, "id">>;
type UserSummary = Pick<ApiUser, "id" | "name">;
type UserResponse = Readonly<ApiUser>;
5. Estratégias de restrição de tipos
(1) Restringir o mais cedo possível
// ❌ The gap is narrowing——Check each location before use
function process(value: string | number) {
console.log(value.toString()); // Only shared methods can be used
if (typeof value === "string") {
console.log(value.toUpperCase());
}
// Further checks will be needed later....
}
// ✅ Narrow as soon as possible——Use directly within the branch
function process2(value: string | number) {
if (typeof value === "string") {
// The entire branch is string
console.log(value.toUpperCase());
console.log(value.trim());
return;
}
// This must be number
console.log(value.toFixed(2));
}
(2) Reutilização da lógica de restrição em verificações de tipo personalizadas
// The Complex Logic Behind Narrowing——Extract as a type guard
function isValidUser(obj: any): obj is User {
return obj
&& typeof obj.id === "number"
&& typeof obj.name === "string"
&& typeof obj.email === "string";
}
// Reuse in Multiple Places
function processUser(data: unknown) {
if (isValidUser(data)) {
console.log(data.name); // ✅ Type Safety
}
}
6. Diretrizes para a colaboração em equipe
(1) Configuração unificada do tsconfig
{
"compilerOptions": {
"strict": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true
}
}
As mesmas regras são aplicadas automaticamente aos IDEs de todos os membros da equipe — não é necessária nenhuma configuração manual.
(2) Regras do ESLint para TypeScript
{
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking"
],
"rules": {
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/explicit-function-return-type": "warn"
}
}
(3) Lista de verificação para revisão de código
- [ ] Não há tipo
any(a menos que um comentário explique o motivo) - [ ] Os parâmetros e valores de retorno das funções possuem anotações de tipo
- [ ] Verifica se há valores que possam ser nulos ou indefinidos
- [ ]
@ts-ignorenão foi utilizado (substituído por@ts-expect-error) - [ ] A API pública inclui comentários JSDoc
- [ ] A importação de tipo utiliza
import type
▶ Exemplo: Modo estrito capturando bugs reais
// strict: true catches null/undefined bugs at compile time
interface SearchResult { items: string[]; total: number; }
function search(query: string): SearchResult | null {
if (!query.trim()) return null;
return { items: [`Result for ${query}`], total: 1 };
}
// Without strict: compiles but crashes at runtime
// let result = search("");
// console.log(result.items.length); // TypeError at runtime
// With strict: compiler forces null check
let result = search("");
if (result) {
console.log(result.items.length); // ✅ safe after narrowing
} else {
console.log("No query provided");
}
Saída:
No query provided
▶ Exemplo: Eliminando any com genéricos
// ❌ Before: loose types with any
function getProp(obj: any, key: string): any {
return obj[key];
}
let user: any = { name: "Alice", age: 30 };
let name2 = getProp(user, "name"); // any — no autocomplete
// ✅ After: generics preserve type information
function getProp2<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
let user2 = { name: "Alice", age: 30 } as const;
let name3 = getProp2(user2, "name"); // "Alice" — exact literal type
let age = getProp2(user2, "age"); // 30 — exact literal type
// getProp2(user2, "email"); // ❌ not assignable to keyof
Saída:
Compile error — Argument of type "email" is not assignable to keyof
❓ Perguntas Frequentes
P: O uso de
anydeve ser totalmente proibido em um projeto? R: Isso não é realista. Configure o ESLint parano-explicit-any: warnem vez deerror— isso permite um pequeno número de usos deany, mas solicita uma revisão.anyé aceitável em cenários como bibliotecas de terceiros sem tipagem, conversões de tipos complexas e prototipagem rápida. O importante é incluir comentários explicando o motivo e ter um plano para a substituição.
P: As definições de tipos devem ser colocadas em um arquivo separado ou dentro do arquivo em que são utilizadas? R: Os tipos públicos/compartilhados devem ser colocados no diretório
types/oumodels/; os tipos utilizados apenas dentro de um único arquivo devem ser definidos diretamente nesse arquivo. Regra: Se um tipo for referenciado por três ou mais arquivos, extraia-o para um arquivo de tipos públicos; caso contrário, defina-o no próprio arquivo em que é usado.
P: Devo usar o ESLint em um projeto TypeScript? R: Sim. O tsc lida com a verificação de tipos, enquanto o ESLint lida com as verificações de qualidade do código — os dois se complementam, em vez de se substituírem. O plugin
@typescript-eslintoferece regras específicas para TypeScript (comono-explicit-anyeconsistent-type-imports), tornando-o uma escolha padrão para projetos em TypeScript.
P: O que devo fazer se houver muitos parâmetros de tipo genérico? R: Se houver mais de três parâmetros de tipo, considere o seguinte: (1) Substitua vários genéricos por parâmetros de objeto; (2) Extraia alguns tipos para interfaces separadas; (3) Use valores padrão genéricos para reduzir o número de parâmetros que devem ser especificados. Um número excessivo de parâmetros de tipo geralmente indica um nível inadequado de abstração.
📖 Resumo
- Convenções de nomenclatura: use PascalCase para interfaces, tipos e classes; use uma única letra ou PascalCase para genéricos
- Design de tipos: precisão em vez de generalidade, derivação em vez de codificação manual, composição em vez de herança
- Alternativas para
any:unknown(solução de segurança), genéricos (preservação de tipos), tipos de união (enumeração explícita) - Tipo DRY: derivado do código-fonte usando
keyof,typeofou tipos utilitários; sem definições duplicadas - Restrição de tipos: restrinja o mais cedo possível; reutilize as verificações de tipo
- Diretrizes da equipe: tsconfig padronizado, regras do ESLint e listas de verificação para revisão de código
📝 Exercícios
- Exercício básico (Dificuldade ⭐): Encontre um exemplo do tipo
anyque você já tenha escrito ou visto e substitua-o porunknownou por tipos genéricos. Certifique-se de que a funcionalidade permaneça a mesma após a substituição e que o código fique mais seguro em termos de tipos. - Problema avançado (Dificuldade ⭐⭐): Refatore um conjunto de definições de tipos usando o princípio DRY — dada a interface
ApiProduct, use tipos utilitários para derivar os quatro tiposCreateProduct,UpdateProduct,ProductSummaryeProductResponse, sem escrever manualmente nenhuma propriedade duplicada. - Desafio (Dificuldade: ⭐⭐⭐): Elabore um documento de padrões de codificação em TypeScript para sua equipe — incluindo convenções de nomenclatura, alternativas ao
any, regras de importação de tipos, configurações recomendadas dotsconfige regras recomendadas do ESLint. Justifique cada regra, acompanhada de exemplos e contraexemplos.