TypeScript: Tipos cruzados e composição avançada no…
Última atualização: 2026-08-26
Os tipos de interseção utilizam & para combinar vários tipos em um único tipo — o novo tipo possui todas as características de cada um dos tipos originais. Trata-se de uma ferramenta essencial para tipos compostos no TypeScript.
1. Noções básicas sobre tipos de cruzamentos
(1) Sintaxe básica
type Named = { name: string };
type Aged = { age: number };
type Person = Named & Aged;
let person: Person = {
name: "Charlie",
age: 20
// Both must be present name and age
};
(2) Comparação com tipos de união
| Operador | Significado | Analogia |
|---|---|---|
A & B (OU) |
Satisfaz tanto A quanto B | E (e) |
| `A | B` (Conjunção) | Satisfaz A ou B |
type StringOrNumber = string | number; // Joint:It could be one of them
type StringAndNumber = string & number; // Crossing:It must be both at the same time → never!
// Meaningful Crossovers——Object Type
type HasId = { id: number };
type HasName = { name: string };
type Entity = HasId & HasName; // ✅ At the same time, there are id and name
(3) Sobreposição entre vários tipos
type Timestamped = { createdAt: Date; updatedAt: Date };
type SoftDeletable = { deletedAt: Date | null };
type Auditable = { createdBy: string; updatedBy: string };
type FullEntity = Timestamped & SoftDeletable & Auditable;
let article: FullEntity = {
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
createdBy: "admin",
updatedBy: "editor"
};
2. Regras de fusão para dados de tipos diferentes
(1) Propriedades com o mesmo nome — determine a interseção dos tipos
Quando dois tipos possuem propriedades com o mesmo nome, mas são de tipos diferentes, o resultado da interseção é a interseção dos dois tipos:
type A = { value: string | number };
type B = { value: string | boolean };
type C = A & B;
// C 's value Type = (string | number) & (string | boolean) = string
let c: C = { value: "hello" }; // ✅ string It is the intersection
// let c2: C = { value: 42 }; // ❌ number Not in the intersection
// let c3: C = { value: true }; // ❌ boolean Not in the intersection
(2) Propriedades com o mesmo nome — tipos incompatíveis resultam em um erro “never”
Quando as propriedades de dois tipos com o mesmo nome não se sobrepõem, o resultado é never — é impossível que tal valor exista:
type A = { id: string };
type B = { id: number };
type C = A & B;
// C 's id Type = string & number = never
// No value can be both string and number
// let c: C = { id: "x" }; // ❌ You cannot string Assigned never
never — ele simplesmente torna o tipo indisponível. Essa é a principal diferença entre tipos cruzados e interface extends: extends gera um erro quando um conflito é detectado, enquanto os tipos cruzados & resultam silenciosamente em never.
(3) Interseção de tipos de função
Quando os tipos de função se cruzam, os parâmetros são considerados como sua interseção (o que geralmente resulta em never):
type StringHandler = (value: string) => void;
type NumberHandler = (value: number) => void;
type MixedHandler = StringHandler & NumberHandler;
// MixedHandler parameters = string & number = never
// In fact, there is no value that can satisfy both signatures at the same time.
3. A diferença entre o tipo cruzado e interface extends
(1) Comparação sintática
// interface extends
interface Person {
name: string;
}
interface Employee extends Person {
employeeId: string;
}
// type Crossing
type Person2 = { name: string };
type Employee2 = Person2 & { employeeId: string };
(2) Comparação entre métodos de resolução de conflitos
| Cenário | interface estende | interseção de tipos & |
|---|---|---|
| Compatibilidade entre tipos de atributos com o mesmo nome | ✅ O subtipo substitui o supertipo | Considere a interseção |
| Tipos de propriedade incompatíveis com o mesmo nome | ❌ Erro de compilação | Gera silenciosamente um never |
| Herança múltipla | Apenas herança única | Herança cruzada múltipla permitida |
| Declaração incorporada | Apoio | Não apoio |
(3) Recomendações de seleção
// Use extends scenario — Compile-time conflict detection is required
interface BaseConfig {
host: string;
port: number;
}
interface DevConfig extends BaseConfig {
debug: boolean; // ✅ New Properties
// host: number; // ❌ Compilation error——Parent Type host: string Conflict
}
// Using Intercut Scenes——Requires flexible combinations of multiple types
type WithTimestamps = { createdAt: Date; updatedAt: Date };
type WithAudit = { createdBy: string; updatedBy: string };
type FullRecord = BaseConfig & WithTimestamps & WithAudit;
// Quick Combinations,No need to define an intermediate step interface
4. Padrões comuns para combinações de tipos
(1) Padrão 1: Mixin
"Incorporar" funcionalidades adicionais a um objeto por meio de tipos de interface:
type WithId = { id: number };
type WithTimestamps = { createdAt: Date; updatedAt: Date };
type WithSoftDelete = { deletedAt: Date | null };
type WithAudit = { createdBy: string; updatedBy: string };
// Freely combine different abilities
type BaseEntity = WithId & WithTimestamps;
type FullEntity = WithId & WithTimestamps & WithSoftDelete & WithAudit;
interface Article extends BaseEntity {
title: string;
content: string;
}
let article: Article = {
id: 1,
createdAt: new Date(),
updatedAt: new Date(),
title: "TypeScript Getting Started",
content: "TypeScript is a superset of JavaScript..."
};
(2) Modelo 2: Combinação de condições
Selecione se deseja marcar um tipo específico com base nas condições:
type EntityWithOptional<T, TExtra> = T & Partial<TExtra>;
interface User {
id: number;
name: string;
}
interface UserProfile {
avatar: string;
bio: string;
}
// User + Optional Profile
type UserWithOptionalProfile = EntityWithOptional<User, UserProfile>;
// { id: number; name: string; avatar?: string; bio?: string }
(3) Modelo Três: Tipos de marca
Use tipos cruzados para “identificar” tipos primitivos e evitar seu uso indevido:
type USD = number & { __brand: "USD" };
type EUR = number & { __brand: "EUR" };
function createUSD(amount: number): USD {
return amount as USD;
}
function createEUR(amount: number): EUR {
return amount as EUR;
}
let price: USD = createUSD(100);
let cost: EUR = createEUR(80);
// price = cost; // ❌ EUR Cannot be assigned to USD
// price + cost; // ❌ Cannot be combined
function addUSD(a: USD, b: USD): USD {
return (a + b) as USD; // Calculations can be made within the same currency
}
let total = addUSD(price, createUSD(50));
console.log(total); // 150
▶ Exemplo: Combinações de configuração com segurança de tipos
// Basic Configuration
type BaseConfig = {
host: string;
port: number;
};
// Additional Development Environment Configuration
type DevConfig = BaseConfig & {
debug: true;
mockApi: boolean;
};
// Additional Configuration for the Production Environment
type ProdConfig = BaseConfig & {
debug: false;
ssl: boolean;
maxConnections: number;
};
function createDevConfig(): DevConfig {
return { host: "localhost", port: 3000, debug: true, mockApi: true };
}
function createProdConfig(): ProdConfig {
return { host: "api.example.com", port: 443, debug: false, ssl: true, maxConnections: 100 };
}
let dev = createDevConfig();
let prod = createProdConfig();
console.log(`Development:${dev.host}:${dev.port} (mock: ${dev.mockApi})`);
console.log(`Production:${prod.host}:${prod.port} (ssl: ${prod.ssl})`);
Saída:
Development:localhost:3000 (mock: true)
Production:api.example.com:443 (ssl: true)
5. Técnicas avançadas para operações entre diferentes tipos
(1) Conversão recursiva de tipos
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object
? T[K] extends Array<any>
? T[K]
: DeepPartial<T[K]>
: T[K];
};
interface Config {
server: { host: string; port: number };
database: { url: string; pool: { min: number; max: number } };
}
type PartialConfig = DeepPartial<Config>;
let cfg: PartialConfig = {}; // All levels are available
(2) Interseções em funções genéricas
function merge<T extends object, U extends object>(a: T, b: U): T & U {
return { ...a, ...b };
}
let person = merge({ name: "Charlie" }, { age: 20 });
// person Type:{ name: string } & { age: number }
(3) Interseções em tipos condicionais
type AddTimestamps<T> = T & { createdAt: Date; updatedAt: Date };
interface User {
name: string;
email: string;
}
type TimestampedUser = AddTimestamps<User>;
// { name: string; email: string; createdAt: Date; updatedAt: Date }
▶ Exemplo: Tipos utilitários construídos com interseção
Saída:
Development: localhost:3000 (mock: true)
Production: api.example.com:443 (ssl: true)
type WithId = { id: number };
type WithTimestamps = { createdAt: Date; updatedAt: Date };
type WithSoftDelete = { deletedAt: Date | null };
type Entity = WithId & WithTimestamps & WithSoftDelete;
type Creatable<T> = Omit<T, "id" | "createdAt" | "updatedAt" | "deletedAt">;
type Updatable<T> = Partial<Omit<T, "id">>;
interface Product extends Entity {
name: string;
price: number;
}
type CreateProduct = Creatable<Product>;
// { name: string; price: number }
type UpdateProduct = Updatable<Product>;
// { name?: string; price?: number; createdAt?: Date; ... }
let newProduct: CreateProduct = { name: "Widget", price: 9.99 };
let update: UpdateProduct = { price: 12.99 };
console.log(update.price); // 12.99
Saída:
Development: localhost:3000 (mock: true)
Production: api.example.com:443 (ssl: true)
▶ Exemplo: Tipos marcados para segurança de domínio
Saída:
Development: localhost:3000 (mock: true)
Production: api.example.com:443 (ssl: true)
type UserId = number & { __brand: "UserId" };
type OrderId = number & { __brand: "OrderId" };
function createUserId(n: number): UserId { return n as UserId; }
function createOrderId(n: number): OrderId { return n as OrderId; }
let uid = createUserId(42);
let oid = createOrderId(99);
// uid = oid; // ❌ OrderId is not assignable to UserId
function findUser(id: UserId): string {
return `User #${id}`;
}
console.log(findUser(uid)); // "User #42"
// console.log(findUser(oid)); // ❌ prevents accidental misuse
Saída:
Development: localhost:3000 (mock: true)
Production: api.example.com:443 (ssl: true)
❓ Perguntas Frequentes
P: Como faço para solucionar um erro “never” causado por interseções de tipos? R: Remova gradualmente os membros que se intersectam para identificar quais dois tipos estão em conflito. Uma causa comum são tipos de propriedade incompatíveis com o mesmo nome (por exemplo,
string & number). Recomendamos usarinterface extendsem vez de interseções —extendsgera um erro imediatamente quando há um conflito, facilitando a identificação do problema.
P: O cross-typing pode substituir a herança? R: Sim, na maioria dos casos, mas não são totalmente equivalentes.
interface extendsoferece detecção de conflitos em tempo de compilação, fusão de declarações e suporte paraclass implements. O cross-typing é mais flexível, mas não possui detecção de conflitos. Recomendamos usarextendspara combinar tipos de objetos e o cross-typing para combinações simples e rápidas.
P: Os tipos com nome próprio são úteis no desenvolvimento prático? R: Eles são muito úteis quando é preciso distinguir entre tipos que têm a mesma estrutura, mas significados diferentes. Exemplos típicos incluem moedas (USD x EUR), IDs (UserId x OrderId) e unidades de medida (metros x pés). Os tipos marcados evitam confusões acidentais e detectam erros — como tratar euros como dólares — já na fase de compilação.
P: Os tipos disjuntivos e os tipos conjuntivos podem ser usados juntos? R: Sim.
type T = (A & B) | (C & D)significa “ou A e B são satisfeitos, ou C e D são satisfeitos”. Os parênteses determinam a precedência —&tem precedência maior do que|, portantoA & B | C & Dé igual a(A & B) | (C & D).
📖 Resumo
- Tipo cruzado
A & BCombina todas as características de vários tipos — o novo tipo deve satisfazer todos os tipos que o compõem simultaneamente - Ao cruzar tipos de propriedade com o mesmo nome, tipos incompatíveis resultam em
never— o TypeScript não reporta ativamente um erro interface extendsinclui detecção de conflitos, enquantotypeoferece mais flexibilidade — escolha de acordo com o contexto- Padrões comuns de combinação: mixins, combinações condicionais e tipos de marca
- Use
& { __brand: "label" }para tipos de marca, a fim de evitar a confusão entre tipos que tenham a mesma estrutura, mas significados diferentes
📝 Exercícios
- Problema básico (Dificuldade ⭐): Defina dois tipos,
WithIdeWithTimestamps, combine-os usando um tipo cruzado para formarBaseEntitye crie um objeto que satisfaçaBaseEntity. - Problema avançado (Dificuldade ⭐⭐): Implemente os tipos de marca
UserId = number & { __brand: "UserId" }eOrderId = number & { __brand: "OrderId" }. Escreva funções para criar cada tipo de ID e verifique se eles não podem ser atribuídos uns aos outros. - Problema de desafio (Dificuldade ⭐⭐⭐): Implemente o tipo de ferramenta
Overwrite<T, U>— substitua as propriedades em T com o mesmo nome pelas propriedades de U, preservando as propriedades com nomes diferentes. Dica: Use um tipo de mapeamento + um tipo cruzado.