Objetos tipados e propriedades opcionais
1. Fundamentos da Tipagem de Objetos
Em TypeScript, a tipagem de objetos começa com a definição explícita da estrutura que um objeto deve seguir. A forma mais básica é o tipo literal de objeto:
const usuario: { nome: string; idade: number } = {
nome: "Ana",
idade: 28
};
Para evitar repetição, criamos type aliases que encapsulam a estrutura do objeto:
type Usuario = {
nome: string;
idade: number;
email: string;
};
const ana: Usuario = {
nome: "Ana",
idade: 28,
email: "ana@exemplo.com"
};
Interfaces oferecem uma alternativa igualmente poderosa, com a vantagem de serem extensíveis:
interface IUsuario {
nome: string;
idade: number;
email: string;
}
interface IUsuarioAdmin extends IUsuario {
nivelAcesso: number;
}
A escolha entre type e interface depende do contexto. Interfaces são preferíveis para definições de contratos públicos e bibliotecas, enquanto types oferecem mais flexibilidade com uniões e interseções.
2. Propriedades Opcionais com ?
Propriedades opcionais permitem que um objeto funcione corretamente mesmo sem determinados campos. A sintaxe utiliza ? após o nome da propriedade:
type Configuracao = {
url: string;
timeout?: number; // opcional
retry?: boolean; // opcional
};
const config1: Configuracao = { url: "https://api.exemplo.com" };
const config2: Configuracao = { url: "https://api.exemplo.com", timeout: 5000 };
É crucial entender a diferença entre prop?: Tipo e prop: Tipo | undefined:
type Exemplo1 = {
campo?: string; // pode não existir OU ser undefined
};
type Exemplo2 = {
campo: string | undefined; // DEVE existir, mas pode ser undefined
};
const obj1: Exemplo1 = {}; // válido
const obj2: Exemplo2 = {}; // erro: propriedade 'campo' está faltando
3. Propriedades Readonly
O modificador readonly impede a reatribuição de uma propriedade após a criação do objeto:
type Ponto = {
readonly x: number;
readonly y: number;
};
const p: Ponto = { x: 10, y: 20 };
p.x = 15; // Erro: não é possível atribuir a 'x' porque é uma propriedade somente leitura
Podemos combinar readonly com opcionais:
type Documento = {
readonly id: string;
readonly dataCriacao?: Date; // opcional e imutável
conteudo: string;
};
Importante: readonly impede a reatribuição da referência, mas não torna o valor profundo imutável. Objetos aninhados ainda podem ser modificados.
4. Tipos de União e Interseção em Objetos
União de tipos de objeto permite que uma variável assuma diferentes estruturas:
type Resposta =
| { status: "sucesso"; dados: unknown }
| { status: "erro"; mensagem: string };
function processarResposta(res: Resposta) {
if (res.status === "sucesso") {
console.log(res.dados); // TypeScript sabe que é seguro
} else {
console.log(res.mensagem);
}
}
Interseção combina múltiplos tipos em um único objeto:
type Identificavel = { id: number };
type Nomeavel = { nome: string };
type Entidade = Identificavel & Nomeavel;
const entidade: Entidade = {
id: 1,
nome: "Exemplo"
};
Cuidado com propriedades conflitantes em interseções:
type A = { valor: number };
type B = { valor: string };
type C = A & B; // 'valor' se torna `never` (number & string é impossível)
5. Index Signatures e Propriedades Dinâmicas
Para objetos com chaves dinâmicas, usamos index signatures:
type Dicionario = {
[chave: string]: number;
};
const precos: Dicionario = {
"maçã": 2.50,
"banana": 1.80
};
Podemos combinar propriedades fixas com dinâmicas:
type Inventario = {
readonly id: string;
[item: string]: number | string; // propriedades dinâmicas
};
const estoque: Inventario = {
id: "INV-001",
"camisetas": 50,
"calças": 30
};
O tipo utilitário Record<K, V> simplifica a criação de dicionários tipados:
type Notas = Record<string, number>;
const notasAlunos: Notas = {
"Ana": 8.5,
"Bruno": 7.0
};
6. Narrowing e Verificação de Propriedades
O operador in verifica a existência de propriedades em tempo de execução:
type Carro = { motor: string; portas: number };
type Bicicleta = { quadro: string };
function exibirDetalhes(veiculo: Carro | Bicicleta) {
if ("motor" in veiculo) {
console.log(`Motor: ${veiculo.motor}`); // TypeScript faz narrowing
} else {
console.log(`Quadro: ${veiculo.quadro}`);
}
}
Para propriedades opcionais, use verificação explícita:
type Perfil = {
nome: string;
idade?: number;
};
function saudacao(perfil: Perfil) {
const idade = perfil.idade ?? "não informada";
console.log(`${perfil.nome}, idade: ${idade}`);
}
Desestruturação com valores padrão oferece segurança:
function configurar({ url, timeout = 3000, retry = false }: Configuracao) {
console.log(`URL: ${url}, Timeout: ${timeout}, Retry: ${retry}`);
}
7. Utility Types para Manipulação de Objetos
TypeScript fornece utility types poderosos para transformar tipos de objetos:
type Usuario = {
nome: string;
email: string;
idade: number;
};
// Partial: todas as propriedades se tornam opcionais
type UsuarioParcial = Partial<Usuario>;
// Required: todas as propriedades se tornam obrigatórias
type UsuarioCompleto = Required<Partial<Usuario>>;
// Pick: seleciona propriedades específicas
type NomeEmail = Pick<Usuario, "nome" | "email">;
// Omit: remove propriedades específicas
type SemIdade = Omit<Usuario, "idade">;
// Readonly: todas as propriedades se tornam imutáveis
type UsuarioImutavel = Readonly<Usuario>;
Exemplo prático combinando utility types:
function atualizarUsuario(
id: number,
mudancas: Partial<Usuario>
): Usuario {
// lógica de atualização
return { ...usuarioOriginal, ...mudancas };
}
8. Boas Práticas e Padrões Comuns
Evite excesso de propriedades opcionais. Muitas opcionais indicam design frágil. Prefira tipos separados ou discriminated unions:
// Ruim: muitas opcionais
type NotificacaoRuim = {
tipo?: string;
mensagem?: string;
destinatario?: string;
};
// Bom: discriminated union
type Notificacao =
| { tipo: "email"; mensagem: string; destinatario: string }
| { tipo: "sms"; mensagem: string; telefone: string };
Documente propriedades opcionais complexas com JSDoc:
type ConfiguracaoAvancada = {
/** URL base da API */
baseUrl: string;
/**
* Timeout em milissegundos.
* @default 5000
*/
timeout?: number;
/**
* Estratégia de retry em caso de falha.
* - 'none': sem retry
* - 'linear': retry com intervalo fixo
* - 'exponential': retry com backoff exponencial
*/
retryStrategy?: 'none' | 'linear' | 'exponential';
};
Prefira interfaces para contratos públicos e types para composições complexas. Use readonly sempre que uma propriedade não deve ser alterada após a inicialização. Para objetos com muitas variantes, discriminated unions oferecem type safety superior a propriedades opcionais.
Dominar objetos tipados e propriedades opcionais em TypeScript é fundamental para escrever código expressivo, seguro e de fácil manutenção. A combinação correta de tipos, utility types e padrões de design eleva significativamente a qualidade do seu código.
Referências
- TypeScript Handbook: Object Types — Documentação oficial sobre tipos de objeto, propriedades opcionais e readonly
- TypeScript Handbook: Utility Types — Referência completa sobre Partial, Required, Pick, Omit e outros utility types
- TypeScript Deep Dive: Type System — Guia aprofundado sobre o sistema de tipos, incluindo uniões, interseções e index signatures
- TypeScript Playground: Object Types Examples — Ambiente interativo para experimentar com tipos de objeto e propriedades opcionais
- Understanding TypeScript: Advanced Object Types — Curso que aborda em detalhes tipos avançados de objetos, utility types e padrões de design
- TypeScript Evolution: Optional Properties — Artigo técnico explicando o comportamento de propriedades opcionais e suas nuances