No desenvolvimento JavaScript moderno, lidar com objetos aninhados e valores indefinidos é uma tarefa comum e, muitas vezes, propensa a erros. Antes do ES2020, acessar propriedades de objetos que poderiam ser null ou undefined exigia verificações manuais ou o uso de operadores lógicos com efeitos colaterais. Com a introdução do optional chaining (?.) e do nullish coalescing (??), o código se torna mais limpo, seguro e expressivo.

Nesta aula, vamos explorar em detalhes como esses operadores funcionam, como combiná-los de forma eficaz e quais armadilhas evitar. Você verá exemplos práticos que ilustram o uso no dia a dia, desde acessos a APIs até configurações de objetos com valores padrão.

?. (Encadeamento Opcional)

O operador de encadeamento opcional (?.) permite acessar propriedades de um objeto de forma segura, sem lançar um erro se a referência for null ou undefined. Ele funciona como o operador de acesso a propriedade (.), mas interrompe a avaliação e retorna undefined se o valor antes do operador for nullish (ou seja, null ou undefined).

Antes do ES2020, para acessar uma propriedade aninhada com segurança, você precisava escrever algo como:

// Antes do ES2020
const nome = usuario && usuario.perfil && usuario.perfil.nome;

Com o encadeamento opcional, o mesmo código fica muito mais limpo:

const nome = usuario?.perfil?.nome;

Se usuario for null ou undefined, a expressão retorna undefined sem lançar erro. O mesmo vale para usuario.perfil. Você também pode usar ?.[] para acessar propriedades dinâmicas e ?.() para chamar funções de forma segura.

Exemplo com acesso dinâmico:

const dados = { lista: [10, 20, 30] };
const primeiro = dados?.lista?.[0]; // 10
const segundo = dados?.lista?.[1]; // 20
const inexistente = dados?.outraLista?.[0]; // undefined

Exemplo com chamada de função:

const objeto = {
  metodo: () => 'Olá'
};
const resultado = objeto.metodo?.(); // 'Olá'

const objetoVazio = {};
const resultado2 = objetoVazio.metodo?.(); // undefined

?? (Coalescência Nula)

O operador de coalescência nula (??) é um operador lógico que retorna o operando do lado direito quando o operando do lado esquerdo é null ou undefined. Caso contrário, retorna o operando do lado esquerdo. Ele foi criado para fornecer um valor padrão apenas para valores nullish, ao contrário do operador || que considera também outros valores falsy (como 0, '', false, NaN).

Veja a diferença:

const valor = 0;
const comOu = valor || 'padrão'; // 'padrão' (porque 0 é falsy)
const comNullish = valor ?? 'padrão'; // 0 (porque 0 não é nullish)

Isso é particularmente útil quando você quer preservar valores como 0 ou '' como válidos, mas ainda assim fornecer um fallback para ausência de valor.

Exemplo prático:

function configurar(opcoes) {
  const volume = opcoes.volume ?? 50; // Se volume for undefined, usa 50
  const nome = opcoes.nome ?? 'Visitante'; // Se nome for undefined, usa 'Visitante'
  return { volume, nome };
}

console.log(configurar({ volume: 0 })); // { volume: 0, nome: 'Visitante' }
console.log(configurar({ nome: 'Ana' })); // { volume: 50, nome: 'Ana' }

Combinando com Defaults

Uma das combinações mais poderosas é usar o encadeamento opcional junto com o operador de coalescência nula para acessar propriedades aninhadas e fornecer valores padrão de forma concisa. Isso é muito comum em aplicações que consomem APIs ou configuram objetos com muitos níveis.

Por exemplo, suponha que você tenha um objeto de usuário vindo de uma API e queira obter o nome do primeiro telefone, com um fallback:

const usuario = {
  contatos: {
    telefones: [{ numero: '123' }]
  }
};

const telefone = usuario?.contatos?.telefones?.[0]?.numero ?? 'Sem telefone';
console.log(telefone); // '123'

// Se faltar algum nível:
const usuario2 = {};
const telefone2 = usuario2?.contatos?.telefones?.[0]?.numero ?? 'Sem telefone';
console.log(telefone2); // 'Sem telefone'

Essa combinação elimina a necessidade de múltiplas verificações if e torna o código muito mais legível.

Outro exemplo comum é configurar opções com valores padrão:

const opcoes = {
  tema: 'escuro',
  notificacoes: {
    email: true
  }
};

const emailNotif = opcoes?.notificacoes?.email ?? false;
const smsNotif = opcoes?.notificacoes?.sms ?? true; // true, pois não foi definido
console.log(emailNotif); // true
console.log(smsNotif); // true

Pegadinhas

Apesar de serem extremamente úteis, esses operadores têm algumas pegadinhas que você deve conhecer para evitar bugs sutis.

1. Não é possível usar ?. com operadores de atribuição — Você não pode escrever obj?.prop = valor, pois isso lançaria um erro de sintaxe. O encadeamento opcional é apenas para leitura (ou chamada de função), não para atribuição. Para atribuir com segurança, você precisa verificar manualmente.

// Errado: SyntaxError
// usuario?.nome = 'João';

// Correto:
if (usuario) {
  usuario.nome = 'João';
}

2. Cuidado com o uso de ?? em expressões com && ou || — O operador ?? tem precedência menor que || e &&, mas não pode ser misturado com eles sem parênteses. Isso gera um erro de sintaxe. Por exemplo:

// Errado: SyntaxError
// const x = a ?? b || c;

// Correto:
const x = (a ?? b) || c;

3. O encadeamento opcional não impede erros em operações subsequentes — Se você acessar uma propriedade que não existe, retorna undefined, mas se você tentar usar esse valor em uma operação aritmética, por exemplo, ainda pode obter NaN ou erro. Por exemplo:

const usuario = { perfil: null };
const idade = usuario?.perfil?.idade * 2; // TypeError: Cannot read properties of null (reading 'idade')

Na verdade, usuario?.perfil?.idade retorna undefined, e undefined * 2 resulta em NaN (não um erro), mas se você tentar acessar uma propriedade de undefined sem o encadeamento, aí sim terá erro. Sempre verifique se o valor retornado é utilizável antes de operar.

4. ?? não pode ser usado com && e || sem parênteses — Já mencionado, mas vale reforçar: a mistura sem parênteses é um erro de sintaxe. Sempre use parênteses para agrupar.

5. Em navegadores antigos, pode ser necessário transpilar — Embora hoje seja suportado em todos os navegadores modernos, se você precisar suportar ambientes mais antigos, use Babel ou TypeScript para transpilar o código.

Boas Práticas

Aqui estão algumas boas práticas ao usar ?. e ??:

  • Use ?. para acessar propriedades em cadeias onde qualquer nível pode ser null ou undefined, especialmente ao trabalhar com dados de APIs.
  • Use ?? para fornecer valores padrão que devem ser aplicados apenas para null/undefined, não para outros valores falsy.
  • Combine ambos para obter código conciso e seguro.
  • Evite usar ?. em cadeias muito longas, pois pode tornar o código difícil de ler; considere extrair variáveis intermediárias se necessário.
  • Sempre teste os casos em que a propriedade não existe para garantir que o valor padrão seja aplicado corretamente.

Referências

Exercícios

  1. Dado o objeto const user = { profile: { name: 'Ana', age: 25 } };, escreva uma expressão que retorne o nome do usuário usando optional chaining. Depois, escreva uma expressão que retorne o nome do perfil de um objeto vazio ({}) sem lançar erro.

    ✓ Resposta:
    // Para o objeto user
    const nome = user?.profile?.name; // 'Ana'
    
    // Para objeto vazio
    const nomeVazio = {}?.profile?.name; // undefined
    
  2. Explique a diferença entre || e ?? ao fornecer um valor padrão. Dê um exemplo onde o uso de || seria inadequado.

    ✓ Resposta:

    || considera qualquer valor falsy (0, '', false, NaN) como ausente, enquanto ?? considera apenas null e undefined. Exemplo: se você quer preservar o valor 0 como válido, usar || substituiria por padrão. Exemplo inadequado:

    const quantidade = 0;
    const comOu = quantidade || 10; // 10 (incorreto, pois 0 é um valor válido)
    const comNullish = quantidade ?? 10; // 0 (correto)
    
  3. Escreva uma função que receba um objeto de configuração e retorne o valor da propriedade timeout com default 3000, usando ??. A propriedade pode estar aninhada em config.request.

    ✓ Resposta:
    function getTimeout(config) {
      return config?.request?.timeout ?? 3000;
    }
    
  4. Qual é a saída do seguinte código? Explique por quê.

    const obj = { a: { b: 0 } };
    console.log(obj?.a?.b ?? 'default');
    console.log(obj?.x?.b ?? 'default');
    

    ✓ Resposta:
    // Saída: 0 e 'default'
    // No primeiro caso, obj.a.b é 0, que não é nullish, então ?? retorna 0.
    // No segundo caso, obj.x é undefined, então a cadeia retorna undefined, e ?? retorna 'default'.
    
  5. Escreva um código que, dado um objeto data que pode ter uma propriedade items (um array), retorne o primeiro item ou null se não existir. Use optional chaining e nullish coalescing.

    ✓ Resposta:
    const primeiroItem = data?.items?.[0] ?? null;