Introdução: por que isso é tão pesquisado?
\n\nCada desenvolvedor que migra do JavaScript para o TypeScript ou simplesmente escreve nessa linguagem, mais cedo ou mais tarde, enfrenta o problema: como definir corretamente um valor padrão para um objeto que é passado para uma função? No Stack Overflow, essa pergunta acumulou milhares de visualizações e dezenas de respostas. Por quê? Porque, aparentemente, uma tarefa simples — "se o objeto não for passado, use um objeto vazio" — no TypeScript se transforma em uma verdadeira aventura com tipos, interfaces e desestruturação.
\n\nNeste artigo, vamos explorar todas as formas possíveis, desde as mais simples até as mais avançadas, com exemplos de código e explicações. Você aprenderá como configurar valores padrão para um objeto TypeScript passado como argumento e evitar erros comuns. Vamos lá!
\n\nO problema: o que há de errado com a abordagem comum?
\n\nSuponha que temos uma função que recebe um objeto com configurações:
\n\nfunction createUser(config: { name?: string; age?: number }) {\n // ...\n}\n\ncreateUser(); // Erro! É necessário um argumento\n\n\nEm JavaScript, poderíamos simplesmente escrever function createUser(config = {}) e pronto. Mas no TypeScript, esse truque não funciona: o compilador reclamará da incompatibilidade de tipos. Por quê? Porque o objeto vazio {} não corresponde à interface { name?: string; age?: number } — ele simplesmente não possui essas propriedades.
E é aqui que a coisa fica interessante. Vamos analisar todas as formas de resolver isso.
\n\nMétodo 1: Desestruturação com atribuição de valores padrão
\n\nO método mais elegante e popular é usar a desestruturação diretamente nos parâmetros da função. Você extrai as propriedades do objeto e já define valores padrão para elas:
\n\nfunction createUser({ name = "Convidado", age = 18 }: { name?: string; age?: number } = {}) {\n console.log(`Usuário criado: ${name}, idade: ${age}`);\n}\n\ncreateUser(); // "Usuário criado: Convidado, idade: 18"\ncreateUser({ name: "Ana" }); // "Usuário criado: Ana, idade: 18"\ncreateUser({ age: 25 }); // "Usuário criado: Convidado, idade: 25"\n\n\nObserve o = {} no final. Este é o valor padrão para o próprio objeto. Se você não passar um argumento, o TypeScript usará um objeto vazio, e a desestruturação aplicará os valores padrão para cada propriedade.
Vantagens: limpo, conciso, funciona perfeitamente.
\nDesvantagens: é necessário especificar explicitamente o tipo para cada propriedade.
Método 2: Uso de interface com Partial
\n\nSe o seu objeto for grande, descrever todos os tipos nos parâmetros é uma má prática. Vamos extrair a interface separadamente e usar Partial<T>:
interface UserConfig {\n name: string;\n age: number;\n email: string;\n isAdmin: boolean;\n}\n\nfunction createUser(config: Partial<UserConfig> = {}) {\n const defaults: UserConfig = {\n name: "Convidado",\n age: 18,\n email: "noemail@example.com",\n isAdmin: false\n };\n \n const mergedConfig = { ...defaults, ...config };\n console.log(mergedConfig);\n}\n\ncreateUser({ name: "João" });\n// { name: "João", age: 18, email: "noemail@example.com", isAdmin: false }\n\n\nAqui, Partial<UserConfig> torna todas as propriedades opcionais. Dentro da função, mesclamos os valores padrão com os valores passados. Funciona perfeitamente, mas requer mesclagem manual.
Método 3: Abordagem clássica com verificação
\n\nÀs vezes, é necessário mais controle. Por exemplo, se você quiser definir um valor padrão apenas para undefined, mas não para null. Usamos o bom e velho if:
function createUser(config?: UserConfig) {\n const name = config?.name ?? "Convidado";\n const age = config?.age ?? 18;\n const email = config?.email ?? "noemail@example.com";\n \n console.log({ name, age, email });\n}\n\ncreateUser();