O JavaScript moderno oferece dois operadores que tornam o código mais seguro e expressivo ao lidar com valores nulos ou indefinidos: o optional chaining (?.) e o nullish coalescing (??). Eles foram introduzidos no ES2020 e rapidamente se tornaram ferramentas essenciais para desenvolvedores. Nesta aula, vamos explorar como usá-los para acessar propriedades aninhadas sem medo de erros, fornecer valores padrão de forma limpa e evitar pegadinhas comuns.

Imagine que você tem um objeto que representa um usuário, e deseja acessar o endereço, mas o endereço pode não existir. Sem esses operadores, você precisaria de várias verificações if ou do operador lógico &&. Com o optional chaining e o nullish coalescing, o código fica muito mais conciso e legível. Vamos ver como isso funciona na prática.

?. (Optional Chaining)

O operador ?. permite que você acesse propriedades de um objeto de forma segura, interrompendo a avaliação se o valor for null ou undefined. Em vez de lançar um erro, ele retorna undefined.

Considere o seguinte objeto:

const user = {
  name: 'Ana',
  address: {
    city: 'São Paulo'
  }
};

console.log(user.address.city); // "São Paulo"

Se address não existir, user.address.city lançará um erro TypeError. Com o optional chaining, você pode escrever:

console.log(user.address?.city); // undefined, sem erro

O ?. também pode ser usado com chamadas de função e acesso a índices. Por exemplo:

const result = someObject.method?.(); // se someObject.method não for função, retorna undefined
const firstItem = array?.[0]; // se array for undefined/null, retorna undefined

Uma observação importante: o optional chaining só verifica o valor imediatamente à esquerda do ?.. Por exemplo, user.address?.city verifica se user.address é null/undefined; se for, retorna undefined. Se user for null, você ainda precisa usar user?.address?.city.

?? (Nullish Coalescing)

O operador ?? fornece um valor padrão quando o operando à esquerda é null ou undefined. Ele é diferente do operador lógico ||, que considera valores falsy como 0, '', false, NaN como 'vazios' e os substitui pelo valor padrão. O ?? é mais preciso: só ativa quando o valor é null ou undefined.

Veja a diferença:

const valor1 = 0;
const valor2 = null;

console.log(valor1 || 'padrão'); // "padrão" (porque 0 é falsy)
console.log(valor1 ?? 'padrão'); // 0 (porque 0 não é null/undefined)

console.log(valor2 || 'padrão'); // "padrão"
console.log(valor2 ?? 'padrão'); // "padrão"

Isso é extremamente útil quando você quer tratar apenas valores ausentes, sem interferir em valores válidos como 0 ou ''.

Combinando com defaults

O optional chaining e o nullish coalescing podem ser combinados para criar padrões de código muito robustos. Por exemplo, ao acessar uma propriedade aninhada que pode não existir, você pode fornecer um valor padrão:

const user = {
  name: 'Ana',
  address: null
};

const city = user.address?.city ?? 'Cidade não informada';
console.log(city); // "Cidade não informada"

Nesse exemplo, se user.address for null, o ?. retorna undefined, e o ?? fornece o valor padrão. Se user.address existir, mas city for null, também retorna o padrão.

Outra aplicação comum é em configurações ou parâmetros de função:

function greet(config) {
  const name = config?.name ?? 'Visitante';
  console.log(`Olá, ${name}!`);
}

greet({ name: 'João' }); // Olá, João!
greet({}); // Olá, Visitante!
greet(null); // Olá, Visitante!

Isso elimina a necessidade de verificações manuais e torna o código mais limpo.

Pegadinhas

Apesar de serem poderosos, esses operadores têm algumas armadilhas que você deve conhecer.

1. Não é possível usar ?. em uma atribuição (exceto em alguns casos com avaliação curta). Por exemplo, obj?.prop = valor é inválido. Você precisa verificar antes de atribuir ou usar lógica alternativa.

2. O ?? não pode ser combinado diretamente com || ou && sem parênteses. Isso gera um erro de sintaxe, pois a mistura é ambígua. Por exemplo, a ?? b || c é inválido; use parênteses: (a ?? b) || c.

3. O optional chaining não impede erros em outras partes da cadeia. Ele só protege o acesso à propriedade imediatamente à esquerda. Se você tiver user.address.street?.name e user.address for null, o erro ocorrerá ao acessar .street antes do ?..

4. Uso em chamadas de função: se você usar obj.method?.(), o ?. verifica se obj.method é null/undefined. Se for, retorna undefined, mas se for outro valor não-chamável (como um número), ele tentará chamar e lançará um erro. Portanto, o ?. não valida se é uma função, apenas se não é null/undefined.

5. Compatibilidade: embora seja amplamente suportado em navegadores modernos, verifique se o seu alvo de compilação (por exemplo, Babel) suporta esses recursos, especialmente em ambientes mais antigos.

Boas Práticas

Ao usar esses operadores, prefira o ?? em vez de || para valores padrão quando você quer preservar valores como 0 ou ''. Use o optional chaining para acessar propriedades aninhadas em objetos que podem ser null/undefined, mas não abuse: se a estrutura for garantida, não é necessário. Sempre teste os casos de borda, como valores falsy e null.

Referências

Exercícios

  1. Dado o objeto const data = { user: { profile: { name: 'Maria' } } }, escreva uma expressão que acesse data.user.profile.age de forma segura, retornando undefined se algo não existir.
  2. Usando o operador ??, escreva uma função que receba um objeto config e retorne o valor de config.retries se for um número, ou 3 como padrão. Considere que config pode ser null/undefined.
  3. Qual é a diferença entre || e ??? Dê um exemplo onde eles produzem resultados diferentes.
  4. Explique por que obj?.prop = value é um erro de sintaxe e sugira uma alternativa.
  5. O que o código a seguir imprime? Justifique sua resposta.
    const a = { b: null };
    console.log(a.b?.c ?? 'padrão');
        

✓ Resposta: A expressão é data.user.profile?.age ou data?.user?.profile?.age. A primeira é suficiente se data.user.profile pode ser null, mas se qualquer um dos níveis pode ser null, use a versão com ?. em todos os níveis.

✓ Resposta: A função pode ser:
function getRetries(config) {
  return config?.retries ?? 3;
}
    
Se config for null/undefined, retorna 3. Se config.retries for null/undefined, também retorna 3. Se for um número (incluindo 0), retorna esse número.

✓ Resposta: O operador || retorna o valor à direita se o valor à esquerda for falsy (0, '', false, NaN, null, undefined). O ?? retorna o valor à direita apenas se o valor à esquerda for null ou undefined. Exemplo: 0 || 'padrão' retorna 'padrão', enquanto 0 ?? 'padrão' retorna 0.

✓ Resposta: O optional chaining não pode ser usado no lado esquerdo de uma atribuição porque a expressão pode resultar em undefined, e atribuir a undefined não faz sentido. Alternativa: verifique se a propriedade existe antes de atribuir, por exemplo: if (obj) obj.prop = value; ou use Object.assign com uma condição.

✓ Resposta: O código imprime "padrão". Porque a.b é null, então a.b?.c retorna undefined, e o ?? fornece o valor padrão "padrão".