O Typecript suporta o? operador? (E como se chama?)


337

O Typescript atualmente (ou há planos para) oferecer suporte ao operador de navegação segura de?.

ou seja:

var thing = foo?.bar
// same as:
var thing = (foo) ? foo.bar : null;

Além disso, existe um nome mais comum para esse operador (é extremamente difícil pesquisar no Google).


3
@mattytommo você tem isso em c #, é chamado de operador coalescente nulo e usa o ?? sintaxe weblogs.asp.net/scottgu/archive/2007/09/20/…
basarat 7/03/13

2
@BasaratAli Infelizmente não, coalesce é bom para property ?? property2, mas se você tentou property.company ?? property1.companye propertyfoi nula, você deseja obter umNullReferenceException
mattytommo

1
@mattytommo Obrigado, eu entendi agora '?.' realmente absorve todas as referências nulas na cadeia. Doce.
basarat

9
@mattytommo isso existe agora para C #: msdn.microsoft.com/en-us/library/dn986595.aspx
Highmastdon 6/15

9
A Microsoft representante que nos visitou o chamou do operador Elvis como os olhares ponto de interrogação como o cabelo de Elvis e um microfone que ele está cantando em ...
Zymotik

Respostas:


168

Atualização : é suportado no TypeScript 3.7 e chamado Encadeamento opcional : https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#optional-chaining

Não consigo encontrar nenhuma referência a ele na especificação da linguagem TypeScript .

Quanto ao que chamar esse operador no CoffeeScript, ele é chamado de operador existencial (especificamente, a "variante acessadora" do operador existencial).

Da documentação do CoffeeScript sobre operadores :

A variante acessadora do operador existencial ?.pode ser usada para absorver referências nulas em uma cadeia de propriedades. Use-o em vez do acessador de ponto .nos casos em que o valor base pode ser nulo ou indefinido .

Portanto, a variante acessadora do operador existencial parece ser a maneira correta de se referir a esse operador; e o TypeScript atualmente não parece suportá-lo (embora outros tenham expressado desejo por essa funcionalidade ).


28
"variante acessadora do operador existencial". Naturalmente. Tão cativante, é quase impossível esquecer. :). Obrigado pela resposta extremamente completa.
Marty Pitt

1
@MartyPitt Sure thing! Concordo que eu adoraria ver a) adoção mais ampla de um operador como este (C #, por favor!) Eb) um nome melhor (o operador de "navegação segura" da postagem do blog vinculado tem um ótimo toque).
31713 Donut

2
O Angular implementa isso em seus modelos: angular.io/guide/…
Enzoaeneas

5
Em algumas outras línguas a sua chamada o operador "Elvis"
k0enf0rNL

4
Foi anunciado para o TypeScript 3.7.0 ( github.com/microsoft/TypeScript/issues/… )
c_froehlich

146

Não é tão legal quanto um single ?, mas funciona:

var thing = foo && foo.bar || null;

Você pode usar quantos && quiser:

var thing = foo && foo.bar && foo.bar.check && foo.bar.check.x || null;

33
&& avalia enquanto a afirmação for verdadeira. Se for verdade, ele retorna o último valor. Se for falso, ele retornará o primeiro valor avaliado como falso. Pode ser 0, nulo, falso etc. || retorna o primeiro valor que é avaliado como verdadeiro.
A. KR

34
Não funciona bem se a barra estiver definida, mas for avaliada como falsa (como booleano falso ou zero).
mt_serg

96

Isso é definido na especificação de encadeamento opcional do ECMAScript, portanto, provavelmente devemos nos referir ao encadeamento opcional quando discutirmos isso. Provável implementação:

const result = a?.b?.c;

O longo e curto deste é que a equipe do TypeScript está aguardando o aperto da especificação do ECMAScript, para que sua implementação possa ser ininterrupta no futuro. Se eles implementassem algo agora, acabaria precisando de grandes mudanças se o ECMAScript redefinisse sua especificação.

Consulte Especificação de encadeamento opcional

Onde algo nunca será o JavaScript padrão, a equipe do TypeScript pode implementar como achar melhor, mas, para futuras adições ao ECMAScript, eles querem preservar a semântica, mesmo que dêem acesso antecipado, como o fazem para muitos outros recursos.

Cortes curtos

Portanto, todos os operadores funky JavaScripts estão disponíveis, incluindo as conversões de tipo, como ...

var n: number = +myString; // convert to number
var b: bool = !!myString; // convert to bool

Solução Manual

Mas voltando à pergunta. Eu tenho um exemplo obtuso de como você pode fazer algo semelhante em JavaScript (e, portanto, em TypeScript), embora eu definitivamente não esteja sugerindo que seja uma graciosa como o recurso que você realmente procura.

(foo||{}).bar;

Portanto, se foois é undefinedo resultado undefinede se fooé definido e tem uma propriedade nomeada barque possui um valor, o resultado é esse valor.

Eu coloquei um exemplo no JSFiddle .

Isso parece bastante superficial para exemplos mais longos.

var postCode = ((person||{}).address||{}).postcode;

Função Cadeia

Se você está desesperado por uma versão mais curta enquanto a especificação ainda está no ar, eu uso esse método em alguns casos. Ele avalia a expressão e retorna um padrão se a cadeia não puder ser satisfeita ou acabar nula / indefinida (observe que !=é importante aqui, não queremos usá-lo !==, pois queremos um pouco de malabarismo positivo aqui).

function chain<T>(exp: () => T, d: T) {
    try {
        let val = exp();
        if (val != null) {
            return val;
        }
    } catch { }
    return d;
}

let obj1: { a?: { b?: string }} = {
    a: {
        b: 'c'
    }
};

// 'c'
console.log(chain(() => obj1.a.b, 'Nothing'));

obj1 = {
    a: {}
};

// 'Nothing'
console.log(chain(() => obj1.a.b, 'Nothing'));

obj1 = {};

// 'Nothing'
console.log(chain(() => obj1.a.b, 'Nothing'));

obj1 = null;

// 'Nothing'
console.log(chain(() => obj1.a.b, 'Nothing'));

1
interessante, mas no meu caso (this.loop || {}).nativeElementdizendo Property 'nativeElement' does not exist on type '{}'. any this.looptypeof angular.io/api/core/ElementRef
kuncevic.dev

@Kuncevic - você precisa ... 1) fornecer um padrão compatível no lugar de {}, ou 2) usar uma asserção de tipo para silenciar o compilador.
Fenton

Supondo que fooé um objeto útil real: (foo || {}).bargeralmente não será compilado em texto datilografado, porque {}não será do mesmo tipo que foo. Esse é o problema que a solução da @ VeganHunter pretende evitar.
111318 Simon_Weaver #

1
@Simon_Weaver then (foo || {bar}). Bar deixará o compilador funcionar sem problemas e acho que a verbosidade é aceitável.
Harps

@harps na verdade isso só compila se bar é definida como uma variável, o que não seria muito provável
Simon_Weaver

83

Atualização: Sim, é suportado agora!

Foi lançado com o TypeScript 3.7: https://devblogs.microsoft.com/typescript/announcing-typescript-3-7/

É chamado de encadeamento opcional : https://devblogs.microsoft.com/typescript/announcing-typescript-3-7/#optional-chaining

Com ele o seguinte:

let x = foo?.bar.baz(); 

é equivalente a:

let x = (foo === null || foo === undefined) ?
    undefined :
    foo.bar.baz();

Resposta antiga

Há uma solicitação de recurso aberto para isso no github, onde você pode expressar sua opinião / desejo: https://github.com/Microsoft/TypeScript/issues/16


36

Editar 13 de novembro de 2019!

Desde 5 de novembro de 2019, o TypeScript 3.7 foi lançado e agora suporta ?. o operador de encadeamento opcional 🎉🎉🍾🍾🎉 !!!

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#optional-chaining


Apenas para fins históricos:

Edit: Eu atualizei a resposta graças ao comentário fracz.

Lançamento do TypeScript 2.0 !.Não é o mesmo que ?.(Navegador seguro em C #)

Veja esta resposta para mais detalhes:

https://stackoverflow.com/a/38875179/1057052

Isso informará apenas o compilador que o valor não é nulo ou indefinido. Isso não verifica se o valor é nulo ou indefinido.

Operador de asserção não nula TypeScript

// Compiled with --strictNullChecks
function validateEntity(e?: Entity) {
    // Throw exception if e is null or invalid entity
}

function processEntity(e?: Entity) {
    validateEntity(e);
    let s = e!.name;  // Assert that e is non-null and access name
}

4
Não é o mesmo que ?porque afirma que o valor está definido. ?espera-se que silenciosamente falhe / avalie como falso. De qualquer forma, é bom saber.
Fracz 3/11

1
Agora que penso nisso ... Essa resposta é inútil, porque ela não faz a "navegação segura" que o operador C # faz.
Jose A

5
Isso respondeu à minha pergunta, no entanto. Eu sabia sobre? do c # e tentei em texto datilografado. Não funcionou, mas eu vi isso! existia, mas não sabia o que fazia. Gostaria de saber se era o mesmo, fiz uma pesquisa no google e encontrei o caminho para essa pergunta que me informou que não, eles são diferentes.
precisa saber é o seguinte

11

O operador de encadeamento opcional do Elvis (?.) É suportado no TypeScript 3.7.

Você pode usá-lo para verificar valores nulos: cats?.miowsretorna nulo se gatos for nulo ou indefinido.

Você também pode usá-lo para chamada de método opcional: cats.doMiow?.(5)chamará doMiow, se existir.

Acesso a propriedade também é possível: cats?.['miows'].

Referência: https://devblogs.microsoft.com/typescript/announcing-typescript-3-7-beta/


Por favor, corrija-me, mas o operador Elvis está pelo menos em Kotlin ?:. Você tem uma referência?
rekire



1
O anúncio da versão TypeScript 3.7 menciona: devblogs.microsoft.com/typescript/announcing-typescript-3-7
György Balássy

10

O operador ?.não é suportado no TypeScript versão 2.0 .

Então, eu uso a seguinte função:

export function o<T>(someObject: T, defaultValue: T = {} as T) : T {
    if (typeof someObject === 'undefined' || someObject === null)
        return defaultValue;
    else
        return someObject;
}

o uso fica assim:

o(o(o(test).prop1).prop2

Além disso, você pode definir um valor padrão:

o(o(o(o(test).prop1).prop2, "none")

Funciona muito bem com o IntelliSense no Visual Studio.


1
Era exatamente isso que eu estava procurando! Funciona em texto datilografado 2.1.6.
Rajab Shakirov 25/03

5
ou você poderia chamá-lo elvis<T>;-)
Simon_Weaver 12/12/18

3
Simon_Weaver, eu chamo de "palhaço triste": o (
VeganHunter 13/11/18

5

Finalmente chegou!

Aqui estão alguns exemplos:

// properties
foo?.bar
foo?.bar()
foo?.bar.baz()
foo?.bar?.baz()

// indexing
foo?.[0]
foo?.['bar']

// check if a function is defined before invoking
foo?.()
foo.bar?.()
foo?.bar?.()

Mas não funciona exatamente da mesma forma que você supõe.

Em vez de avaliar

foo?.bar

neste pequeno trecho de código, estamos todos acostumados a escrever

foo ? foo.bar : null

realmente avalia como

(foo === null || foo === undefined) ?
    undefined :
    foo.bar

que funciona para todos os valores falsey como uma string vazia, 0 ou false.

Eu simplesmente não tenho uma explicação de por que eles não o compilam foo == null


3

Criamos esse método utilitário enquanto trabalhamos no Phonetradr, que pode fornecer acesso seguro a propriedades profundas com o Typecript:

/**
 * Type-safe access of deep property of an object
 *
 * @param obj                   Object to get deep property
 * @param unsafeDataOperation   Function that returns the deep property
 * @param valueIfFail           Value to return in case if there is no such property
 */
export function getInSafe<O,T>(obj: O, unsafeDataOperation: (x: O) => T, valueIfFail?: any) : T {
    try {
        return unsafeDataOperation(obj)
    } catch (error) {
        return valueIfFail;
    }
}

//Example usage:
getInSafe(sellTicket, x => x.phoneDetails.imeiNumber, '');

//Example from above
getInSafe(foo, x => x.bar.check, null);


Legal!! Existe alguma advertência? Eu tenho uma classe de invólucro com cerca de 20 getters para escrever, cada um deles tem o seguinte tipo de retorno - e todos os campos têm que ser nulo verificadoreturn this.entry.fields.featuredImage.fields.file.url;
Drenai

A única ressalva pode ser um impacto no desempenho, mas não estou qualificado para falar sobre como os vários JITers lidam com isso.
21818 Ray Suelzer

2

Geralmente, eu não recomendo essa abordagem (atente a problemas de desempenho), mas você pode usar o operador spread para clonar superficialmente um objeto, no qual você pode acessar a propriedade.

 const person = { personId: 123, firstName: 'Simon' };
 const firstName = { ...person }.firstName;

Isso funciona porque o tipo de 'firstName' é 'propagado'.

Usarei isso com mais frequência quando tiver uma find(...)expressão que possa retornar nula e eu precisar de uma única propriedade:

 // this would cause an error (this ID doesn't exist)
 const people = [person];
 const firstName2 = people.find(p => p.personId == 999).firstName;

 // this works - but copies every property over so raises performance concerns
 const firstName3 = { ...people.find(p => p.personId == 999) }.firstName;

Pode haver alguns casos extremos com a maneira como o texto datilografado infere os tipos e isso não será compilado, mas geralmente deve funcionar.


2

Chama-se encadeamento opcional e está no Typescript 3.7

O encadeamento opcional permite escrever código onde podemos parar imediatamente de executar algumas expressões se encontrarmos um nulo ou indefinido


0

Como respondido anteriormente, ele ainda está sendo considerado, mas está morto na água há alguns anos.

Com base nas respostas existentes, aqui está a versão manual mais concisa em que consigo pensar:

jsfiddle

function val<T>(valueSupplier: () => T): T {
  try { return valueSupplier(); } catch (err) { return undefined; }
}

let obj1: { a?: { b?: string }} = { a: { b: 'c' } };
console.log(val(() => obj1.a.b)); // 'c'

obj1 = { a: {} };
console.log(val(() => obj1.a.b)); // undefined
console.log(val(() => obj1.a.b) || 'Nothing'); // 'Nothing'

obj1 = {};
console.log(val(() => obj1.a.b) || 'Nothing'); // 'Nothing'

obj1 = null;
console.log(val(() => obj1.a.b) || 'Nothing'); // 'Nothing'

Simplesmente falha silenciosamente devido a erros de propriedade ausentes. Ele retorna à sintaxe padrão para determinar o valor padrão, que também pode ser omitido completamente.


Embora isso funcione em casos simples, se você precisar de coisas mais complexas, como chamar uma função e acessar uma propriedade no resultado, outros erros também serão engolidos. Projeto ruim.

No caso acima, uma versão otimizada da outra resposta postada aqui é a melhor opção:

jsfiddle

function o<T>(obj?: T, def: T = {} as T): T {
    return obj || def;
}

let obj1: { a?: { b?: string }} = { a: { b: 'c' } };
console.log(o(o(o(obj1).a)).b); // 'c'

obj1 = { a: {} };
console.log(o(o(o(obj1).a)).b); // undefined
console.log(o(o(o(obj1).a)).b || 'Nothing'); // 'Nothing'

obj1 = {};
console.log(o(o(o(obj1).a)).b || 'Nothing'); // 'Nothing'

obj1 = null;
console.log(o(o(o(obj1).a)).b || 'Nothing'); // 'Nothing'

Um exemplo mais complexo:

o(foo(), []).map((n) => n.id)

Você também pode ir para o outro lado e usar algo como o Lodash ' _.get(). É conciso, mas o compilador não poderá julgar a validade das propriedades usadas:

console.log(_.get(obj1, 'a.b.c'));

0

Ainda não (em setembro de 2019), mas como o "operador de navegação segura" está agora no estágio 3 , está sendo implementado no TypeScript.

Assista a este problema para atualizações:

https://github.com/microsoft/TypeScript/issues/16

Vários mecanismos têm implementações iniciais:

JSC: https://bugs.webkit.org/show_bug.cgi?id=200199

V8: https://bugs.chromium.org/p/v8/issues/detail?id=9553

SM: https://bugzilla.mozilla.org/show_bug.cgi?id=1566143

(via https://github.com/tc39/proposal-optional-chaining/issues/115#issue-475422578 )

Você pode instalar um plugin para suportá-lo agora:

npm install --save-dev ts-optchain

No seu tsconfig.json:

// tsconfig.json
{
    "compilerOptions": {
        "plugins": [
            { "transform": "ts-optchain/transform" },
        ]
    },
}

Espero que essa resposta esteja desatualizada nos próximos 6 meses, mas espero que ajude alguém nesse meio tempo.


-1

_.get(obj, 'address.street.name')funciona muito bem para JavaScript onde você não tem tipos. Mas, para o TypeScript , precisamos do verdadeiro operador Elvis!

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.