Typescript supporta il?. operatore? (E come si chiama?)


337

Typescript attualmente (o ci sono piani per) supporta l' operatore di navigazione sicura di?.

vale a dire:

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

Inoltre, esiste un nome più comune per questo operatore (è incredibilmente difficile per Google).


3
@mattytommo ce l'hai in c #, si chiama operatore null coalescente e usa il ?? sintassi weblogs.asp.net/scottgu/archive/2007/09/20/…
basarat

2
@BasaratAli Sfortunatamente no, la coalizione va bene per property ?? property2, ma se ci provassi property.company ?? property1.companye propertyfosse nulla, otterrai unNullReferenceException
mattytommo

1
@mattytommo Grazie ora capisco '?.' assorbe effettivamente tutti i riferimenti null nella catena. Dolce.
basarat

9
@mattytommo questo esiste ora C #: msdn.microsoft.com/en-us/library/dn986595.aspx
Highmastdon

9
Il rappresentante Microsoft che ci ha visitato lo ha chiamato operatore Elvis poiché il punto interrogativo assomiglia ai capelli di Elvis e al microfono in cui canta ...
Zymotik,

Risposte:


168

Aggiornamento : è supportato da TypeScript 3.7 e chiamato Concatenamento opzionale : https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#optional-chaining

Non riesco a trovare alcun riferimento ad esso nella specifica del linguaggio TypeScript .

Per quanto riguarda come chiamare questo operatore in CoffeeScript, si chiama operatore esistenziale (in particolare, la "variante di accesso" dell'operatore esistenziale).

Dalla documentazione di CoffeeScript sugli operatori :

La variante accessor dell'operatore esistenziale ?.può essere utilizzata per assorbire riferimenti null in una catena di proprietà. Usalo al posto del punto di accesso .nei casi in cui il valore di base può essere nullo o indefinito .

Quindi, la variante accessor dell'operatore esistenziale sembra essere il modo corretto di fare riferimento a questo operatore; e TypeScript al momento non sembra supportarlo (sebbene altri abbiano espresso il desiderio di questa funzionalità ).


28
"variante accessor dell'operatore esistenziale". Naturalmente. Così accattivante, è quasi impossibile da dimenticare. :). Grazie per la risposta estremamente approfondita.
Marty Pitt,

1
@MartyPitt Sicuro! Sono d'accordo, mi piacerebbe vedere a) una più ampia adozione di un operatore come questo (C # per favore!) Eb) un nome migliore (l'operatore "navigazione sicura" dal tuo post sul blog collegato ha un bel suono).
Ciambella

2
Angular implementa questo nei suoi template: angular.io/guide/…
Enzoaeneas

5
In alcune altre lingue si chiama operatore "Elvis"
k0enf0rNL

4
È stato annunciato per TypeScript 3.7.0 ( github.com/microsoft/TypeScript/issues/… )
c_froehlich,

146

Non bello come un singolo?, Ma funziona:

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

Puoi usare quanti && vuoi:

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

33
&& valuta finché l'affermazione è vera. Se è vero, restituisce l'ultimo valore. Se è falso, restituisce il primo valore valutato come falso. Può essere 0, null, false ecc. || restituisce il primo valore che restituisce vero.
A. KR,

34
Non funziona bene se la barra è definita ma restituisce false (come boolean false o zero).
mt_serg,

96

Questo è definito nelle specifiche del concatenamento opzionale ECMAScript, quindi dovremmo probabilmente fare riferimento al concatenamento opzionale quando ne discutiamo. Probabile implementazione:

const result = a?.b?.c;

Il lungo e breve di questo è che il team di TypeScript sta aspettando che le specifiche ECMAScript vengano rafforzate, quindi la loro implementazione potrebbe non essere interrotta in futuro. Se implementassero qualcosa adesso, finirebbe per richiedere grandi cambiamenti se ECMAScript ridefinisse le loro specifiche.

Vedi le specifiche di concatenamento opzionali

Laddove qualcosa non sarà mai standard JavaScript, il team di TypeScript può implementare come meglio crede, ma per future aggiunte di ECMAScript, vogliono preservare la semantica anche se danno accesso anticipato, come hanno fatto per tante altre funzionalità.

Tagli corti

Quindi sono disponibili tutti gli operatori funky di JavaScripts, incluse le conversioni di tipo come ...

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

Soluzione manuale

Ma torniamo alla domanda. Ho un esempio ottuso di come puoi fare una cosa simile in JavaScript (e quindi in TypeScript) anche se non sto assolutamente suggerendo che sia una grazia come la funzionalità che stai veramente cercando.

(foo||{}).bar;

Quindi se fooè undefinedil risultato è undefinede se fooè definito e ha una proprietà denominata barche ha un valore, il risultato è quel valore.

Ho fatto un esempio su JSFiddle .

Questo sembra abbastanza impreciso per esempi più lunghi.

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

Funzione a catena

Se sei alla disperata ricerca di una versione più breve mentre le specifiche sono ancora in sospeso, utilizzo questo metodo in alcuni casi. Valuta l'espressione e restituisce un valore predefinito se la catena non può essere soddisfatta o finisce nullo / indefinito (nota che !=qui è importante, non vogliamo usarlo !==perché vogliamo un po 'di giocoleria positiva qui).

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 ma nel mio caso (this.loop || {}).nativeElementdire Property 'nativeElement' does not exist on type '{}'. any this.looptypeof angular.io/api/core/ElementRef
kuncevic.dev

@Kuncevic - è necessario o ... 1) fornire un valore predefinito compatibile al posto di {}, oppure 2) utilizzare un'asserzione di tipo per mettere a tacere il compilatore.
Fenton,

Supponendo che foosia un vero oggetto utile: (foo || {}).bargeneralmente non verrà compilato in dattiloscritto perché {}non sarà dello stesso tipo di foo. Questo è il problema che la soluzione di @ VeganHunter mira ad evitare.
Simon_Weaver,

1
@Simon_Weaver then (foo || {bar}) .bar consentirà al compilatore di funzionare senza intoppi e penso che la verbosità sia accettabile.
Harps

@harps in realtà questo compila solo se la barra è definita come una variabile, che molto probabilmente non sarebbe
Simon_Weaver

83

Aggiornamento: Sì, ora è supportato!

È appena stato rilasciato con TypeScript 3.7: https://devblogs.microsoft.com/typescript/announcing-typescript-3-7/

Si chiama concatenamento opzionale : https://devblogs.microsoft.com/typescript/announcing-typescript-3-7/#optional-chaining

Con esso il seguente:

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

è equivalente a:

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

Vecchia risposta

C'è una richiesta di funzionalità aperta per questo su github in cui puoi esprimere la tua opinione / desiderio: https://github.com/Microsoft/TypeScript/issues/16


36

Modifica il 13 novembre 2019!

Dal 5 novembre 2019 TypeScript 3.7 è stato spedito e ora supporta ?. l'operatore di concatenamento opzionale 🎉🎉🍾🍾🎉 !!!

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


Solo per scopi storici:

Modifica: ho aggiornato la risposta grazie al commento fracz.

Rilascio di TypeScript 2.0 !.Non è lo stesso di ?.(Navigatore sicuro in C #)

Vedi questa risposta per maggiori dettagli:

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

Questo dirà solo al compilatore che il valore non è nullo o indefinito. Ciò non verificherà se il valore è nullo o indefinito.

TypeScript Operatore di asserzione non null

// 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
Non è lo stesso ?perché afferma che il valore è definito. ?ci si aspetta che fallisca in modo silenzioso / valuti false. Comunque, buono a sapersi.
dal

1
Ora che ci penso ... Questa risposta è abbastanza inutile, perché non fa la "navigazione sicura" dell'operatore C #.
Jose A

5
Questo ha risposto alla mia domanda, però. Ne sapevo? da c # e l'ho provato in dattiloscritto. Non ha funzionato, ma l'ho visto! esisteva ma non sapeva cosa facesse. Mi chiedevo se fosse lo stesso, ho fatto una ricerca su Google e ho trovato la mia strada per questa domanda che mi ha informato che no, sono diversi.
Llewey,

11

L'operatore di concatenamento opzionale Elvis (?.) È supportato in TypeScript 3.7.

Puoi usarlo per controllare i valori null: cats?.miowsrestituisce null se gatti è null o indefinito.

Puoi anche usarlo per chiamare il metodo opzionale: cats.doMiow?.(5)chiamerà doMiow se esiste.

Accesso alla proprietà è anche possibile: cats?.['miows'].

Riferimento: https://devblogs.microsoft.com/typescript/announcing-typescript-3-7-beta/


Per favore, correggimi, ma l'operatore Elvis è almeno a Kotlin ?:. Hai un riferimento?
rekire il


1
Presto sarà supportato in JS semplice - developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Mottie

1
L'annuncio della versione 3.7 di TypeScript lo menziona: devblogs.microsoft.com/typescript/announcing-typescript-3-7
György Balássy

10

L'operatore ?.non è supportato in TypeScript versione 2.0 .

Quindi uso la seguente funzione:

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

l'utilizzo è simile al seguente:

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

inoltre, puoi impostare un valore predefinito:

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

Funziona davvero bene con IntelliSense in Visual Studio.


1
Questo e 'esattamente quello che stavo cercando! Funziona in dattiloscritto 2.1.6.
Rajab Shakirov,

5
o potresti chiamarlo elvis<T>;-)
Simon_Weaver

3
Simon_Weaver, lo chiamo "pagliaccio triste": o (
VeganHunter

5

È finalmente arrivato!

Ecco alcuni esempi:

// 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?.()

Ma non funziona esattamente come il tuo presupposto.

Invece di valutare

foo?.bar

a questo piccolo frammento di codice siamo tutti abituati a scrivere

foo ? foo.bar : null

valuta effettivamente

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

che funziona per tutti i valori di falso come una stringa vuota, 0 o falso.

Non ho una spiegazione sul perché non la compilino foo == null


3

Abbiamo creato questo metodo util mentre lavoravamo su Phonetradr che può darti un accesso sicuro alle proprietà profonde con Typescript:

/**
 * 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);


Freddo!! C'è qualche avvertimento? Ho una classe wrapper con circa 20 getter da scrivere, ognuno di loro ha il seguente tipo di ritorno - e tutti i campi devono essere controllati nullreturn this.entry.fields.featuredImage.fields.file.url;
Drenai

L'unico avvertimento potrebbe essere un impatto sulle prestazioni, ma non sono qualificato per parlare di come gestiscono i vari JITer.
Ray Suelzer,

2

In genere non consiglio questo approccio (attenzione alle preoccupazioni relative alle prestazioni), ma è possibile utilizzare l'operatore di diffusione per clonare superficialmente un oggetto, sul quale è quindi possibile accedere alla proprietà.

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

Questo funziona perché il tipo di 'firstName' è 'propagato' attraverso.

Lo userò più frequentemente quando ho find(...)un'espressione che può restituire null e ho bisogno di una sola proprietà da essa:

 // 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;

Ci possono essere alcuni casi limite con il modo in cui dattiloscrive i tipi e questo non verrà compilato, ma in genere dovrebbe funzionare.


2

Si chiama concatenamento opzionale ed è in Typescript 3.7

Il concatenamento facoltativo ci consente di scrivere codice in cui possiamo immediatamente interrompere l'esecuzione di alcune espressioni se ci imbattiamo in un valore nullo o indefinito


0

Come già detto in precedenza, è ancora in fase di studio, ma ormai è morto nell'acqua da alcuni anni.

Sulla base delle risposte esistenti, ecco la versione manuale più concisa che mi viene in mente:

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'

Semplicemente fallisce silenziosamente in caso di errori di proprietà mancanti. Ritorna alla sintassi standard per determinare il valore predefinito, che può anche essere omesso completamente.


Anche se questo funziona per casi semplici, se hai bisogno di cose più complesse come chiamare una funzione e quindi accedere a una proprietà sul risultato, anche tutti gli altri errori vengono ingoiati. Cattivo design.

Nel caso precedente, una versione ottimizzata dell'altra risposta pubblicata qui è l'opzione migliore:

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'

Un esempio più complesso:

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

Puoi anche andare dall'altra parte e usare qualcosa come Lodash ' _.get(). È conciso, ma il compilatore non sarà in grado di giudicare la validità delle proprietà utilizzate:

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

0

Non ancora (a settembre 2019), ma poiché "l'operatore di navigazione sicura" è ora allo stadio 3 , viene implementato in TypeScript.

Guarda questo problema per gli aggiornamenti:

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

Diversi motori hanno implementazioni anticipate:

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

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

È possibile installare un plug-in per supportarlo ora:

npm install --save-dev ts-optchain

Nel tuo tsconfig.json:

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

Mi aspetto che questa risposta non sia aggiornata nei prossimi 6 mesi circa, ma spero che nel frattempo possa aiutare qualcuno.


-1

_.get(obj, 'address.street.name')funziona benissimo per JavaScript dove non hai tipi. Ma per TypeScript abbiamo bisogno del vero operatore Elvis!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.