Satisfies e as: quando usar cada operador de tipo
1. Introdução aos operadores de tipo no TypeScript
TypeScript oferece duas ferramentas poderosas para lidar com tipos em situações onde o compilador não consegue inferir ou validar automaticamente: as (type assertion) e satisfies (type satisfaction). Embora ambos lidem com a relação entre valores e tipos, eles resolvem problemas fundamentalmente diferentes.
O operador as é uma afirmação de tipo — você diz ao compilador "confie em mim, eu sei o que estou fazendo". Ele existe desde as primeiras versões do TypeScript e permite forçar um tipo sobre um valor, mesmo que o compilador discorde.
Já o operador satisfies, introduzido no TypeScript 4.9, é uma validação de tipo — você diz ao compilador "verifique se este valor está de acordo com este tipo, mas mantenha o tipo mais específico que você inferir". Ele não altera o tipo inferido, apenas valida a conformidade.
A diferença crucial: as altera o tipo para o que você declarar; satisfies valida sem perder informações.
2. as (Type Assertion): quando e como usar
O as tem cenários legítimos, mas seu uso deve ser controlado. Os principais casos incluem:
- APIs DOM: quando você sabe que um elemento tem um tipo específico
- Dados de terceiros: quando você confia na estrutura de dados externos
- Migração de código legado: como ponte temporário para tipagem gradual
// Cenário legítimo: DOM API
const appElement = document.getElementById('app') as HTMLDivElement;
appElement.innerHTML = 'Hello';
// Cenário arriscado: supressão de erro
const input = document.getElementById('input') as HTMLInputElement;
// Se o elemento não existir, isso lançará erro em runtime
O as const é uma variação especial que transforma literais em tipos literais imutáveis:
const colors = ['red', 'green', 'blue'] as const;
// Tipo: readonly ["red", "green", "blue"]
// Não é string[], mas uma tupla literal
const status = 'active' as const;
// Tipo: "active" (não string)
O risco do as é a supressão de erros legítimos:
const user = JSON.parse('{"name": "Alice"}') as { name: string; age: number };
console.log(user.age); // undefined em runtime, mas TypeScript não reclama
3. satisfies: validação sem perda de informação
O satisfies resolve um problema clássico: validar que um objeto está de acordo com um tipo amplo, sem perder os tipos específicos de suas propriedades.
type Config = Record<string, string | number>;
// Sem satisfies: tipo inferido é Config (perde detalhes)
const config1: Config = {
port: 3000,
host: 'localhost'
};
// config1.port é string | number (perdemos que é number)
// Com satisfies: valida mas mantém tipos específicos
const config2 = {
port: 3000,
host: 'localhost'
} satisfies Config;
// config2.port é number (tipo original preservado)
O benefício é claro: podemos acessar propriedades com tipos precisos:
type Palette = Record<string, string | { r: number; g: number; b: number }>;
const palette = {
red: '#ff0000',
blue: { r: 0, g: 0, b: 255 },
green: '#00ff00'
} satisfies Palette;
palette.red.toUpperCase(); // OK: string
palette.blue.r; // OK: number (não precisamos de type guard)
4. Comparação direta: as vs satisfies
| Aspecto | as |
satisfies |
|---|---|---|
| Segurança | Baixa (supressão) | Alta (validação) |
| Inferência | Perde tipo original | Preserva tipo original |
| Perda de info | Sim | Não |
Uso com any/unknown |
Necessário | Não funciona |
Quando as é a única opção:
const data: unknown = JSON.parse('{"id": 1}');
// Precisamos de as para usar o valor
const user = data as { id: number };
Quando satisfies é superior:
type Shape =
| { type: 'circle'; radius: number }
| { type: 'rectangle'; width: number; height: number };
const shape = { type: 'circle', radius: 5 } satisfies Shape;
// shape.type é "circle" (não string)
// shape.radius é number
Exemplo lado a lado:
// Com as: perde informação
const colors1 = {
primary: '#333',
secondary: '#666'
} as Record<string, string>;
// colors1.primary é string (ok, mas poderia ser mais específico)
// Com satisfies: mantém literais
const colors2 = {
primary: '#333',
secondary: '#666'
} satisfies Record<string, string>;
// colors2.primary é "#333" (string literal)
5. Padrões avançados com satisfies
Combinando satisfies com tipos genéricos e keyof:
type EventMap = {
click: { x: number; y: number };
keydown: { key: string };
};
const handlers = {
click: (e: { x: number; y: number }) => console.log(e.x, e.y),
keydown: (e: { key: string }) => console.log(e.key)
} satisfies { [K in keyof EventMap]: (e: EventMap[K]) => void };
// handlers.click tem tipo (e: { x: number; y: number }) => void
// handlers.keydown tem tipo (e: { key: string }) => void
Uso em funções de alta ordem:
function createValidator<T>(schema: T) {
return (data: unknown): data is T => {
// lógica de validação
return true;
};
}
const userValidator = createValidator({
name: 'string',
age: 'number'
} satisfies Record<string, 'string' | 'number'>);
Limitações: satisfies não funciona com tipos condicionais complexos:
// Isso não funciona como esperado
type IsString<T> = T extends string ? true : false;
const test = "hello" satisfies IsString<typeof "hello">; // Erro
6. Armadilhas comuns e boas práticas
Erro frequente: usar as quando satisfies seria mais seguro:
// ❌ Ruim: perde verificação de propriedades extras
const config = { port: 3000, unknownProp: true } as Record<string, number>;
// ✅ Bom: valida e mantém tipos
const config = { port: 3000 } satisfies Record<string, number>;
// Se adicionar unknownProp, TypeScript reclama
as em objetos literais: perde verificação de propriedades extras:
interface User { name: string; age: number; }
// ❌ Não reclama de propriedade extra
const user1 = { name: 'Alice', age: 30, admin: true } as User;
// ✅ Reclama de admin não estar em User
const user2 = { name: 'Alice', age: 30, admin: true } satisfies User;
satisfies com as const: garantindo imutabilidade e validação:
const ROLES = {
ADMIN: 'admin',
USER: 'user'
} as const satisfies Record<string, string>;
// Tipo: { readonly ADMIN: "admin"; readonly USER: "user" }
// Valida que todos os valores são strings
Recomendação geral: prefira satisfies para validação e as apenas para escapes controlados (como any/unknown ou DOM APIs).
7. Conclusão e guia de decisão
A escolha entre as e satisfies segue um fluxo simples:
- Você está lidando com
anyouunknown? → Useas(é a única opção) - Você precisa validar um objeto contra um tipo amplo? → Use
satisfies - Você está acessando APIs DOM? → Use
as(ou considere type narrowing) - Você quer criar literais imutáveis? → Use
as const - Você quer validar sem perder tipos específicos? → Use
satisfies
Regras de ouro:
- Segurança primeiro: satisfies sempre que possível
- as é um escape, não um padrão
- Documente todo uso de as com comentários explicando o porquê
- Prefira type narrowing (type guards, discriminated unions) sobre as
O TypeScript evolui para tornar o satisfies cada vez mais útil. Com a introdução de pattern matching e type narrowing mais avançados em versões futuras, a tendência é que as se torne cada vez mais raro em código bem tipado.
Referências
- TypeScript 4.9 Release Notes: satisfies Operator — Documentação oficial da Microsoft sobre a introdução do operador
satisfiesno TypeScript 4.9 - TypeScript Handbook: Type Assertions — Seção oficial do manual do TypeScript sobre type assertions com o operador
as - TypeScript Deep Dive: satisfies vs as — Guia comparativo detalhado entre
satisfieseascom exemplos práticos - Total TypeScript: The satisfies Operator — Tutorial interativo sobre o operador
satisfiescom exercícios práticos - TypeScript Evolution: satisfies Operator Deep Dive — Análise técnica aprofundada do operador
satisfies, incluindo casos de uso avançados e comparações com alternativas