Unterstützt Typescript das? Operator? (Und wie heißt es?)


334

Unterstützt Typescript derzeit (oder gibt es Pläne) den sicheren Navigationsoperator von ??.

dh:

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

Gibt es auch einen gebräuchlicheren Namen für diesen Operator (es ist unglaublich schwer zu googeln).


3
@mattytommo Sie haben das in c #, es heißt der Null-Koaleszenz-Operator und verwendet den ?? Syntax weblogs.asp.net/scottgu/archive/2007/09/20/…
Basarat

2
@ BasaratAli Leider nicht, Koaleszenz ist in Ordnung für property ?? property2, aber wenn Sie versucht haben property.company ?? property1.companyund propertynull war, würden Sie einNullReferenceException
mattytommo

1
@mattytommo Danke, ich verstehe es jetzt '?' tränkt tatsächlich alle Nullreferenzen in der Kette. Süss.
Basarat

9
@ Mattytommo dies existiert jetzt für C #: msdn.microsoft.com/en-us/library/dn986595.aspx
Highmastdon

9
Der Microsoft-Mitarbeiter, der uns besuchte, nannte es den Elvis-Operator, da das Fragezeichen wie Elvis 'Haare und ein Mikrofon aussieht, in das er singt ...
Zymotik

Antworten:


165

Update : Es wird ab TypeScript 3.7 unterstützt und als optionale Verkettung bezeichnet : https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#optional-chaining

Ich kann in der TypeScript-Sprachspezifikation keinerlei Verweis darauf finden .

Was diesen Operator in CoffeeScript betrifft, wird er als existenzieller Operator bezeichnet (insbesondere als "Accessor-Variante" des existenziellen Operators).

Aus der CoffeeScript-Dokumentation zu Operatoren :

Die Accessor-Variante des existenziellen Operators ?.kann verwendet werden, um Nullreferenzen in einer Eigenschaftskette aufzunehmen. Verwenden Sie es anstelle des Punktzugriffs .in Fällen, in denen der Basiswert null oder undefiniert sein kann .

Die Accessor-Variante des existenziellen Operators scheint also der richtige Weg zu sein, um auf diesen Operator zu verweisen. und TypeScript scheint dies derzeit nicht zu unterstützen (obwohl andere den Wunsch nach dieser Funktionalität geäußert haben ).


28
"Accessor-Variante des existenziellen Operators". Natürlich. So eingängig, dass man es kaum vergessen kann. :). Vielen Dank für die äußerst gründliche Antwort.
Marty Pitt

1
@ MartyPitt Sicher! Ich stimme zu, ich würde gerne a) eine breitere Akzeptanz eines solchen Operators (C # bitte!) Und b) einen besseren Namen sehen (der Operator "sichere Navigation" aus Ihrem verlinkten Blog-Beitrag hat einen schönen Klang).
Donut

2
Angular implementiert dies in seinen Vorlagen: angle.io/guide/…
Enzoaeneas

5
In einigen anderen Sprachen heißt es "Elvis" -Operator
k0enf0rNL

4
Es ist angekündigt für TypeScript 3.7.0 ( github.com/microsoft/TypeScript/issues/… )
c_froehlich

145

Nicht so schön wie eine Single?, Aber es funktioniert:

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

Sie können so viele && verwenden, wie Sie möchten:

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

33
&& wertet aus, solange die Aussage wahr ist. Wenn dies der Fall ist, wird der letzte Wert zurückgegeben. Wenn es falsch ist, wird der erste Wert zurückgegeben, der als falsch ausgewertet wurde. Das kann 0, null, falsch usw. sein. || Gibt den ersten Wert zurück, der als wahr ausgewertet wird.
A. KR

34
Funktioniert nicht gut, wenn der Balken definiert ist, aber als falsch ausgewertet wird (wie boolescher Wert falsch oder null).
mt_serg

96

Dies ist in der Spezifikation für die optionale Verkettung von ECMAScript definiert. Daher sollten wir uns bei der Erörterung wahrscheinlich auf die optionale Verkettung beziehen . Voraussichtliche Umsetzung:

const result = a?.b?.c;

Das lange und kurze daran ist, dass das TypeScript-Team darauf wartet, dass die ECMAScript-Spezifikation verschärft wird, damit ihre Implementierung in Zukunft nicht mehr unterbrochen werden kann. Wenn sie jetzt etwas implementieren würden, müssten größere Änderungen vorgenommen werden, wenn ECMAScript ihre Spezifikation neu definiert.

Siehe Optionale Verkettungsspezifikation

Wenn etwas niemals Standard-JavaScript sein wird, kann das TypeScript-Team es nach eigenem Ermessen implementieren. Für zukünftige ECMAScript-Ergänzungen möchten sie jedoch die Semantik beibehalten, auch wenn sie wie bei so vielen anderen Funktionen frühzeitig darauf zugreifen.

Abkürzungen

So sind alle funky JavaScripts-Operatoren verfügbar, einschließlich der Typkonvertierungen wie ...

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

Manuelle Lösung

Aber zurück zur Frage. Ich habe ein stumpfes Beispiel dafür, wie Sie in JavaScript (und damit in TypeScript) etwas Ähnliches tun können, obwohl ich definitiv nicht vorschlage, dass es eine anmutige Funktion ist, nach der Sie wirklich suchen.

(foo||{}).bar;

Wenn fooalso undefineddas Ergebnis ist undefinedund wenn foodefiniert ist und eine Eigenschaft barmit einem Wert hat, ist das Ergebnis dieser Wert.

Ich habe ein Beispiel für JSFiddle gegeben .

Dies sieht für längere Beispiele recht lückenhaft aus.

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

Kettenfunktion

Wenn Sie verzweifelt nach einer kürzeren Version suchen, während die Spezifikation noch in der Luft ist, verwende ich diese Methode in einigen Fällen. Es wertet den Ausdruck aus und gibt einen Standardwert zurück, wenn die Kette nicht erfüllt werden kann oder null / undefiniert endet (beachten Sie !=, dass dies hier wichtig ist, wir möchten es nicht verwenden, !==da wir hier ein bisschen positives Jonglieren wollen).

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
interessant, aber in meinem Fall (this.loop || {}).nativeElementsagt Property 'nativeElement' does not exist on type '{}'. any this.looptypeof angle.io/api/core/ElementRef
kuncevic.dev

@Kuncevic - Sie müssen entweder ... 1) einen kompatiblen Standard anstelle von bereitstellen {}oder 2) eine Typzusicherung verwenden, um den Compiler zum Schweigen zu bringen.
Fenton

Angenommen, es foohandelt sich um ein tatsächlich nützliches Objekt: Im (foo || {}).barAllgemeinen wird es nicht in Typoskript kompiliert, da {}es nicht vom selben Typ ist wie foo. Das ist das Problem, das mit der Lösung von @ VeganHunter vermieden werden soll.
Simon_Weaver

1
@Simon_Weaver dann (foo || {bar}). Bar lässt den Compiler reibungslos laufen und ich denke, dass Ausführlichkeit akzeptabel ist.
Harfen

@harps eigentlich kompiliert dies nur, wenn bar als Variable definiert ist, was es höchstwahrscheinlich nicht sein würde
Simon_Weaver

81

Update: Ja, es wird jetzt unterstützt!

Es wurde gerade mit TypeScript 3.7 veröffentlicht: https://devblogs.microsoft.com/typescript/announcing-typescript-3-7/

Es wird als optionale Verkettung bezeichnet : https://devblogs.microsoft.com/typescript/announcing-typescript-3-7/#optional-chaining

Damit folgendes:

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

ist äquivalent zu:

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

Alte Antwort

Auf github gibt es hierfür eine offene Funktionsanforderung, in der Sie Ihre Meinung / Ihren Wunsch äußern können: https://github.com/Microsoft/TypeScript/issues/16


36

Bearbeiten 13. November 2019!

Ab dem 5. November 2019 wurde TypeScript 3.7 ausgeliefert und unterstützt jetzt ?. den optionalen Verkettungsoperator 🎉🎉🍾🍾🎉 !!!

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


Nur für historische Zwecke:

Bearbeiten: Ich habe die Antwort dank fracz Kommentar aktualisiert.

TypeScript 2.0 veröffentlicht !.Es ist nicht dasselbe wie ?.(Safe Navigator in C #)

Siehe diese Antwort für weitere Details:

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

Dies teilt dem Compiler nur mit, dass der Wert nicht null oder undefiniert ist. Das wird nicht überprüft, ob der Wert null oder undefiniert ist.

TypeScript Nicht-Null-Assertionsoperator

// 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
Nicht dasselbe, ?weil behauptet wird, dass der Wert definiert ist. ?wird erwartet, dass es stillschweigend fehlschlägt / als falsch bewertet wird. Wie auch immer, gut zu wissen.
Fracz

1
Jetzt, wo ich darüber nachdenke ... Diese Antwort ist ziemlich sinnlos, weil sie nicht die "sichere Navigation" macht, die der C # -Operator macht.
Jose A

5
Dies beantwortete jedoch meine Frage. Ich wusste von? von c # und versuchte es in Typoskript. Es hat nicht funktioniert, aber das habe ich gesehen! existierte, wusste aber nicht, was es tat. Ich fragte mich, ob es dasselbe war, führte eine Google-Suche durch und fand den Weg zu dieser Frage, die mich darüber informierte, dass sie unterschiedlich sind.
Llewey

11

Der optionale Verkettungsoperator Elvis (?.) Wird in TypeScript 3.7 unterstützt.

Sie können damit nach Nullwerten suchen: cats?.miows Gibt null zurück, wenn Katzen null oder undefiniert sind.

Sie können es auch für optionale Methodenaufrufe verwenden: cats.doMiow?.(5) ruft doMiow auf, falls vorhanden.

Der Zugang zu Immobilien ist ebenfalls möglich : cats?.['miows'].

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


Bitte korrigieren Sie mich, aber der Elvis-Betreiber ist zumindest in Kotlin ?:. Haben Sie eine Referenz?
rekire


1
Es wird bald in einfachem JS unterstützt - developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Mottie

1
Die Ankündigung der TypeScript 3.7-Veröffentlichung erwähnt dies: devblogs.microsoft.com/typescript/announcing-typescript-3-7
György Balássy

10

Der Operator ?.wird in TypeScript Version 2.0 nicht unterstützt .

Also benutze ich folgende Funktion:

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

Die Verwendung sieht folgendermaßen aus:

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

Außerdem können Sie einen Standardwert festlegen:

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

Es funktioniert sehr gut mit IntelliSense in Visual Studio.


1
Genau das habe ich gesucht! Es funktioniert in Typoskript 2.1.6.
Rajab Shakirov

5
oder man könnte es nennen elvis<T>;-)
Simon_Weaver

3
Simon_Weaver, ich nenne es "trauriger Clown": o (
VeganHunter

5

Es ist endlich da!

Hier einige Beispiele:

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

Aber es funktioniert nicht genau so wie Ihre Annahme.

Anstatt zu bewerten

foo?.bar

An dieses kleine Code-Snippet sind wir alle gewöhnt zu schreiben

foo ? foo.bar : null

es wertet tatsächlich aus

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

Dies funktioniert für alle Falsey-Werte wie eine leere Zeichenfolge, 0 oder false.

Ich habe nur keine Erklärung dafür, warum sie es nicht kompilieren foo == null


3

Wir haben diese util-Methode während der Arbeit an Phonetradr erstellt, mit der Sie mit Typescript typsicher auf tiefe Eigenschaften zugreifen können:

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


Cool!! Gibt es irgendwelche Einschränkungen? Ich habe eine Wrapper-Klasse mit ungefähr 20 Gettern zu schreiben, jeder von ihnen hat die folgende Art der Rückgabe - und alle Felder müssen null geprüft werdenreturn this.entry.fields.featuredImage.fields.file.url;
Drenai

Die einzige Einschränkung könnte möglicherweise eine Auswirkung auf die Leistung sein, aber ich bin nicht qualifiziert, darüber zu sprechen, wie die verschiedenen JITer damit umgehen.
Ray Suelzer

2

Ich empfehle diesen Ansatz im Allgemeinen nicht (achten Sie auf Leistungsprobleme), aber Sie können den Spread-Operator verwenden, um ein Objekt flach zu klonen, auf das Sie dann auf die Eigenschaft zugreifen können.

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

Dies funktioniert, weil der Typ von 'Vorname' durch 'weitergegeben' wird.

Ich werde dies am häufigsten verwenden, wenn ich einen find(...)Ausdruck habe, der null zurückgeben kann und eine einzelne Eigenschaft davon benötige:

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

Es kann einige Randfälle mit der Art und Weise geben, wie Typoskript Typen ableitet, und dies wird nicht kompiliert, aber dies sollte im Allgemeinen funktionieren.


2

Es heißt optionale Verkettung und ist in Typescript 3.7

Durch die optionale Verkettung können wir Code schreiben, mit dem wir die Ausführung einiger Ausdrücke sofort beenden können, wenn wir auf null oder undefiniert stoßen


0

Wie bereits erwähnt, wird derzeit noch darüber nachgedacht, aber es ist bereits seit einigen Jahren tot im Wasser .

Aufbauend auf den vorhandenen Antworten ist hier die prägnanteste manuelle Version, die ich mir vorstellen kann:

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'

Bei fehlenden Eigenschaftsfehlern schlägt dies einfach stillschweigend fehl. Es wird auf die Standardsyntax zur Bestimmung des Standardwerts zurückgegriffen, die ebenfalls vollständig weggelassen werden kann.


Obwohl dies in einfachen Fällen funktioniert, werden auch andere Fehler verschluckt, wenn Sie komplexere Dinge wie das Aufrufen einer Funktion und den Zugriff auf eine Eigenschaft für das Ergebnis benötigen. Schlechtes Design.

Im obigen Fall ist eine optimierte Version der anderen hier veröffentlichten Antwort die bessere Option:

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'

Ein komplexeres Beispiel:

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

Sie können auch in die andere Richtung gehen und so etwas wie Lodash 'verwenden _.get(). Es ist kurz, aber der Compiler kann die Gültigkeit der verwendeten Eigenschaften nicht beurteilen:

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

0

Noch nicht (Stand September 2019), aber da sich der "Operator für sichere Navigation" jetzt in Phase 3 befindet , wird er in TypeScript implementiert.

In dieser Ausgabe finden Sie Updates:

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

Einige Motoren haben frühe Implementierungen:

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

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

Sie können jetzt ein Plugin installieren, um es zu unterstützen:

npm install --save-dev ts-optchain

In Ihrer tsconfig.json:

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

Ich gehe davon aus, dass diese Antwort in den nächsten 6 Monaten veraltet sein wird, aber hoffentlich hilft sie in der Zwischenzeit jemandem.


-1

_.get(obj, 'address.street.name')funktioniert hervorragend für JavaScript, wo Sie keine Typen haben. Aber für TypeScript brauchen wir den echten Elvis-Operator!

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.