Encadeamento opcional
Cadeias longas de acessos a propriedades em JavaScript podem ser propensas a erros, já que qualquer uma delas pode ser avaliada como null
ou undefined
(também conhecidos como valores “nulos”). Verificar a existência de propriedade em cada etapa facilmente se transforma em uma estrutura profundamente aninhada de instruções if
ou uma condição if
longa que replica a cadeia de acesso à propriedade:
// Versão propensa a erros, pode lançar exceção.
const nameLength = db.user.name.length;
// Menos propensa a erros, mas mais difícil de ler.
let nameLength;
if (db && db.user && db.user.name)
nameLength = db.user.name.length;
O exemplo acima também pode ser expresso usando o operador ternário, o que não ajuda exatamente na legibilidade:
const nameLength =
(db
? (db.user
? (db.user.name
? db.user.name.length
: undefined)
: undefined)
: undefined);
Apresentando o operador de encadeamento opcional
Certamente você não quer escrever código assim, então ter alguma alternativa é desejável. Algumas outras linguagens oferecem uma solução elegante para esse problema usando um recurso chamado “encadeamento opcional”. De acordo com uma proposta recente de especificação, “uma cadeia opcional é uma cadeia de um ou mais acessos a propriedades e chamadas de função, sendo que a primeira começa com o token ?.
”.
Usando o novo operador de encadeamento opcional, podemos reescrever o exemplo acima da seguinte forma:
// Ainda verifica erros e é muito mais legível.
const nameLength = db?.user?.name?.length;
O que acontece quando db
, user
ou name
é undefined
ou null
? Com o operador de encadeamento opcional, o JavaScript inicializa nameLength
como undefined
em vez de lançar um erro.
Observe que esse comportamento também é mais robusto do que nossa verificação para if (db && db.user && db.user.name)
. Por exemplo, e se name
fosse sempre garantido como uma string? Poderíamos mudar name?.length
para name.length
. Então, se name
fosse uma string vazia, ainda obteríamos o comprimento correto de 0
. Isso ocorre porque a string vazia é um valor falsy: ela se comporta como false
em uma cláusula if
. O operador de encadeamento opcional corrige essa fonte comum de bugs.
Formas adicionais de sintaxe: chamadas e propriedades dinâmicas
Também há uma versão do operador para chamar métodos opcionais:
// Estende a interface com um método opcional, presente
// apenas para usuários administradores.
const adminOption = db?.user?.validateAdminAndGetPrefs?.().option;
A sintaxe pode parecer inesperada, já que ?.()
é o operador real, que se aplica à expressão antes dele.
Há um terceiro uso do operador, que é o acesso opcional a propriedades dinâmicas, feito via ?.[]
. Ele retorna o valor referenciado pelo argumento nos colchetes, ou undefined
caso não haja objeto para obter o valor. Aqui está um caso de uso possível, seguindo o exemplo acima:
// Estende as capacidades do acesso à propriedade estática
// com um nome de propriedade gerado dinamicamente.
const optionName = 'configuração opcional';
const optionLength = db?.user?.preferences?.[optionName].length;
Esta última forma também está disponível para indexar arrays opcionalmente, por exemplo:
// Se `usersArray` for `null` ou `undefined`,
// então `userName` avalia graciosamente para `undefined`.
const userIndex = 42;
const userName = usersArray?.[userIndex].name;
O operador de encadeamento opcional pode ser combinado com o operador de fusão nula ??
quando é necessário um valor padrão não undefined
. Isso permite acesso seguro a propriedades profundas com um valor padrão especificado, abordando um caso de uso comum que anteriormente exigia bibliotecas externas, como _.get
do lodash:
const object = { id: 123, names: { first: 'Alice', last: 'Smith' }};
{ // Com lodash:
const firstName = _.get(object, 'names.first');
// → 'Alice'
const middleName = _.get(object, 'names.middle', '(sem nome do meio)');
// → '(sem nome do meio)'
}
{ // Com encadeamento opcional e fusão nula:
const firstName = object?.names?.first ?? '(sem primeiro nome)';
// → 'Alice'
const middleName = object?.names?.middle ?? '(sem nome do meio)';
// → '(sem nome do meio)'
}
Propriedades do operador de encadeamento opcional
O operador de encadeamento opcional tem algumas propriedades interessantes: curto-circuito, empilhamento e remoção opcional. Vamos passar por cada uma delas com um exemplo.
Curto-circuito significa não avaliar o restante da expressão se um operador de encadeamento opcional retornar cedo:
// `age` é incrementado apenas se `db` e `user` estiverem definidos.
db?.user?.grow(++age);
Encadeamento significa que mais de um operador de encadeamento opcional pode ser aplicado em uma sequência de acessos de propriedades:
// Um encadeamento opcional pode ser seguido por outro encadeamento opcional.
const firstNameLength = db.users?.[42]?.names.first.length;
Ainda assim, seja cuidadoso ao usar mais de um operador de encadeamento opcional em um único encadeamento. Se um valor é garantido que não será nulo ou indefinido, então usar ?.
para acessar propriedades nele é desencorajado. No exemplo acima, db
é considerado sempre definido, mas db.users
e db.users[42]
podem não estar. Se houver tal usuário no banco de dados, então names.first.length
é assumido como sempre definido.
Deleção opcional significa que o operador delete
pode ser combinado com um encadeamento opcional:
// `db.user` é deletado apenas se `db` estiver definido.
delete db?.user;
Mais detalhes podem ser encontrados na seção Semântica da proposta.