Branded Types | Typescript
- Branded Types | Typescript
Branded types (ou nominal typing) em TypeScript são uma técnica para criar tipos distintos mesmo quando compartilham a mesma estrutura — algo que o TypeScript não faz nativamente, já que ele usa structural typing.
Eles são extremamente úteis para evitar erros como confundir:
UserIdcomProductIdCelsiuscomFahrenheitEmailcomstringcomum- etc.
✅ Como criar Branded Types
type Branded<T, B> = T & { __brand: B };
Exemplo: IDs distintos
type UserId = Branded<string, "UserId">;
type ProductId = Branded<string, "ProductId">;
function getUser(id: UserId) {}
function getProduct(id: ProductId) {}
const userId = "123" as UserId;
const productId = "123" as ProductId;
✔️ Isso funciona:
getUser(userId);
getProduct(productId);
❌ Isso NÃO funciona:
getUser(productId); // erro de tipo
Mesmo ambos sendo string, o TypeScript passa a tratá-los como tipos diferentes.
🔒 Evitando que o usuário crie IDs “do nada”
Se quiser restringir ainda mais, crie factory functions:
function makeUserId(id: string): UserId {
return id as UserId;
}
function makeProductId(id: string): ProductId {
return id as ProductId;
}
Agora:
const x = "abc" as UserId; // possível, mas feio
const y = makeUserId("abc"); // recomendado
⚖️ Branded Types para números
type Celsius = Branded<number, "Celsius">;
type Fahrenheit = Branded<number, "Fahrenheit">;
const tempC = 20 as Celsius;
const tempF = 68 as Fahrenheit;
function toFahrenheit(c: Celsius): Fahrenheit {
return ((c * 9) / 5 + 32) as Fahrenheit;
}
🧠 Boas práticas
✔️ Use nomes de brands descritivos
type OrderId = Branded<string, "OrderId">;
✔️ Crie helper genéricos
type Brand<B extends string> = { __brand: B };
type Branded<T, B extends string> = T & Brand<B>;
✔️ Esconda as brands (elas não existem em runtime)
Branded types são zero-cost: não mudam nada no JavaScript final.
⭐ Brand vs. Nominal Types vs. Opaque Types
Brand
“Marca” só para o TypeScript → técnica mais comum.
Opaque Types (estilo FP)
Você não consegue acessar o tipo interno fora do módulo:
// module A
type Email = string & { readonly __brand: unique symbol };
export function makeEmail(v: string): Email {
return v as Email;
}
// module B
const x: Email = "a@b.com"; // erro → criar só com makeEmail