Schreiben Sie den sicheren Navigationsoperator (?.) Oder (!.) Und die Null-Eigenschaftspfade


108

In Angular2-Vorlagen ?.funktioniert der sichere Operator , jedoch nicht in component.tsTypeScript 2.0. Außerdem funktioniert der sichere Navigationsoperator (!) Nicht.

Beispielsweise...

Dieses TypeScript

if (a!.b!.c) { }

Kompiliert zu diesem JavaScript

if (a.b.c) { }

Aber wenn ich es starte, erhalte ich den folgenden Fehler:

Die Eigenschaft 'b' von undefined kann nicht gelesen werden

Gibt es eine Alternative zu den folgenden?

if (a && a.b && a.b.c) { }

7
Die Typoskriptoperatoren existieren nur zum Kompilieren, sie sind im kompilierten Javascript nicht vorhanden. Der von Ihnen veröffentlichte Fehler ist ein Laufzeitfehler.
— Nitzan Tomer

o (o (o (o (Test) .level1) .level2) .level3 oder o (o (o (o (a) .b) .c) .d stackoverflow.com/a/35169378/3914072 Diese Lösung funktioniert hervorragend für uns zur Kompilierungszeit und es ist mit sicheren Typen
— Rajab Shakirov

Antworten:


68

Seit der Veröffentlichung von TypeScript 3.7 können Sie jetzt die optionale Verkettung verwenden.

Eigenschaftsbeispiel:

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

Dies ist gleichbedeutend mit:

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

Außerdem können Sie anrufen:

Optionaler Anruf

function(otherFn: (par: string) => void) {
   otherFn?.("some value");
}

otherFn wird nur aufgerufen, wenn otherFn nicht gleich null oder undefiniert ist

Verwenden Sie die optionale Verkettung in der IF-Anweisung

Dies:

if (someObj && someObj.someProperty) {
    // ...
}

kann jetzt damit ersetzt werden

if (someObj?.someProperty) {
    // ...
}

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


Dies löst beim Kompilieren immer wieder den erwarteten Ausdruck Fehler aus. Es funktioniert, wenn ich !anstelle von ?"Typoskript": "~ 3.5.3"
— Scaramouche

3
@Scaramouche Dies liegt daran, dass der optionale Verkettungsoperator ( ?.) in 3.7 eingeführt wurde und Sie 3.5.3 ausführen. In früheren Versionen als 3.7 geht TypeScript davon aus, ?dass in der Kurzform if form ( foo ? bar : baz) verwendet wird, und erwartet daher den Rest des Ausdrucks
— George

134

!ist ein Nicht-Null-Assertion-Operator (Post-Fix-Ausdruck) - er sagt nur, dass Sie checker eingeben sollen, dass Sie sicher sind , dass dies anicht der Fall ist nulloder undefined.

Die Operation a!erzeugt einen Wert vom Typ amit nullund undefinedausgeschlossen


Die optionale Verkettung erreichte schließlich das Typoskript ( 3.7 ) 🎉

Der optionale Verkettungsoperator ?.ermöglicht das Lesen des Werts einer Eigenschaft, die sich tief in einer Kette verbundener Objekte befindet, ohne dass ausdrücklich überprüft werden muss, ob jede Referenz in der Kette gültig ist. Der ?.Operator funktioniert ähnlich wie der .Verkettungsoperator, außer dass der Ausdruck einen Fehler mit einem Rückgabewert von kurzschließt, anstatt einen Fehler zu verursachen, wenn eine Referenz null ( nulloder undefined) ist undefined. Bei Verwendung mit Funktionsaufrufen wird zurückgegeben, undefinedwenn die angegebene Funktion nicht vorhanden ist.

Syntax :

obj?.prop // Accessing object's property
obj?.[expr] // Optional chaining with expressions
arr?.[index] // Array item access with optional chaining
func?.(args) // Optional chaining with function calls

Achten Sie darauf :

Die optionale Verkettung ist auf der linken Seite einer Aufgabe nicht gültig

const object = {};
object?.property = 1; // Uncaught SyntaxError: Invalid left-hand side in assignment

12
Für diejenigen, die sich über den Status informieren möchten. TypeScript plant, es hinzuzufügen, sobald ECMAScript Unterstützung hinzufügt. ECMAScript verfolgt dieses Problem hier .
— Pace

o (o (o (o (Test) .level1) .level2) .level3 oder o (o (o (o (a) .b) .c) .d stackoverflow.com/a/35169378/3914072 Diese Lösung funktioniert hervorragend für uns zur Kompilierungszeit und es ist mit sicheren Typen
— Rajab Shakirov

Spielplatz der neuen Funktion (wird am 5. November veröffentlichen): typescriptlang.org/play/?ts=3.7.0-pr-33294-11#code/...
— Nisim Joseph

1
@ aleksey-l: Es ist vielleicht keine schlechte Idee, die Dinge vor dem "letzten Update" durchzuarbeiten. Es wird Menschen helfen, die nicht von oben nach unten lesen (dh zu schnell)! :)
— Aidin

1
Das ist so cool - ich hatte keine Ahnung, dass man es für Funktionsaufrufe verwenden könnte. Ich habe nach einer Lösung gesucht und bin auf Ihre Antwort gestoßen. Vielen Dank!
— Geoff Davids

22

Aktualisieren:

Geplant im Rahmen der Version 3.7
https://github.com/microsoft/TypeScript/issues/33352


Sie können versuchen, eine solche benutzerdefinierte Funktion zu schreiben.

Der Hauptvorteil des Ansatzes ist eine Typprüfung und eine teilweise Intelligenz.

export function nullSafe<T, 
    K0 extends keyof T, 
    K1 extends keyof T[K0],
    K2 extends keyof T[K0][K1],
    K3 extends keyof T[K0][K1][K2],
    K4 extends keyof T[K0][K1][K2][K3],
    K5 extends keyof T[K0][K1][K2][K3][K4]>
    (obj: T, k0: K0, k1?: K1, k2?: K2, k3?: K3, k4?: K4, k5?: K5) {
    let result: any = obj;

    const keysCount = arguments.length - 1;
    for (var i = 1; i <= keysCount; i++) {
        if (result === null || result === undefined) return result;
        result = result[arguments[i]];
    }

    return result;
}

Und Verwendung (unterstützt bis zu 5 Parameter und kann erweitert werden):

nullSafe(a, 'b', 'c');

Beispiel auf einem Spielplatz .


1
Das funktioniert gut, aber es gibt ein Problem - VSCode zeigt dies nicht an, wenn "Alle Referenzen suchen" für einen der Schlüssel verwendet wird.
— Ivan Koshelev

Welche Art der Typprüfung? In dem Moment, in dem Sie Zeichenfolgen als Parameter übergeben, haben Sie den Typ verloren. Wenn ja, ist es besser, nur lodash.get zu verwenden
— un33k

16

Eine andere Alternative, die eine externe Bibliothek verwendet, ist _.has () von Lodash .

Z.B

_.has(a, 'b.c')

entspricht

(a && a.b && a.b.c)

BEARBEITEN: Wie in den Kommentaren erwähnt, verlieren Sie bei Verwendung dieser Methode die Typinferenz von Typescript. Unter der Annahme, dass die eigenen Objekte richtig eingegeben wurden, würde man einen Kompilierungsfehler mit (a && ab && abz) erhalten, wenn z nicht als Objektfeld definiert ist. B. Aber mit _.has (a, 'b.z') würde man diesen Fehler nicht bekommen.


Netter Tipp, obwohl keine andere Lösung verfügbar ist, scheint dies eine gute Alternative zu sein.
— Dimas Crocco

3
Aber wie funktioniert es mit der Typinferenz von TypeScript?
— VitalyB

@VitalyB wahrscheinlich meinst du den Import der entsprechenden Schnittstelle von lodash. (Import * als _ aus 'lodash';)
— Ankur Arora

1
@AnkurArora, ich denke, was VitalyB bedeutet, ist, dass keine Typprüfung mit der Lodash-Methode durchgeführt wird. Unter der Annahme, dass die eigenen Objekte richtig eingegeben wurden, würde man einen Kompilierungsfehler mit (a && ab && abz) erhalten, wenn z nicht als Objektfeld definiert ist. B. Aber mit _.has (a, 'b.z') würde man diesen Fehler nicht bekommen. Die import-Anweisung wird definitiv benötigt.
— TiaanM

1
@ TiaanM Genau das habe ich gemeint. Ich habe jedoch gerade eine neue Bibliothek gefunden, die das Typproblem
— behandelt

3

Eine neue Bibliothek namens ts-optchain bietet diese Funktionalität und schützt im Gegensatz zur lodash-Lösung auch Ihre Typen. Hier ein Beispiel für ihre Verwendung (aus der Readme -Datei entnommen):

import { oc } from 'ts-optchain';

interface I {
  a?: string;
  b?: {
    d?: string;
  };
  c?: Array<{
    u?: {
      v?: number;
    };
  }>;
  e?: {
    f?: string;
    g?: () => string;
  };
}

const x: I = {
  a: 'hello',
  b: {
    d: 'world',
  },
  c: [{ u: { v: -100 } }, { u: { v: 200 } }, {}, { u: { v: -300 } }],
};

// Here are a few examples of deep object traversal using (a) optional chaining vs
// (b) logic expressions. Each of the following pairs are equivalent in
// result. Note how the benefits of optional chaining accrue with
// the depth and complexity of the traversal.

oc(x).a(); // 'hello'
x.a;

oc(x).b.d(); // 'world'
x.b && x.b.d;

oc(x).c[0].u.v(); // -100
x.c && x.c[0] && x.c[0].u && x.c[0].u.v;

oc(x).c[100].u.v(); // undefined
x.c && x.c[100] && x.c[100].u && x.c[100].u.v;

oc(x).c[100].u.v(1234); // 1234
(x.c && x.c[100] && x.c[100].u && x.c[100].u.v) || 1234;

oc(x).e.f(); // undefined
x.e && x.e.f;

oc(x).e.f('optional default value'); // 'optional default value'
(x.e && x.e.f) || 'optional default value';

// NOTE: working with function value types can be risky. Additional run-time
// checks to verify that object types are functions before invocation are advised!
oc(x).e.g(() => 'Yo Yo')(); // 'Yo Yo'
((x.e && x.e.g) || (() => 'Yo Yo'))();

2

Aufbauend auf der Antwort von @ Pvl können Sie die Typensicherheit auch in Ihren zurückgegebenen Wert aufnehmen, wenn Sie Überschreibungen verwenden:

function dig<
  T,
  K1 extends keyof T
  >(obj: T, key1: K1): T[K1];

function dig<
  T,
  K1 extends keyof T,
  K2 extends keyof T[K1]
  >(obj: T, key1: K1, key2: K2): T[K1][K2];

function dig<
  T,
  K1 extends keyof T,
  K2 extends keyof T[K1],
  K3 extends keyof T[K1][K2]
  >(obj: T, key1: K1, key2: K2, key3: K3): T[K1][K2][K3];

function dig<
  T,
  K1 extends keyof T,
  K2 extends keyof T[K1],
  K3 extends keyof T[K1][K2],
  K4 extends keyof T[K1][K2][K3]
  >(obj: T, key1: K1, key2: K2, key3: K3, key4: K4): T[K1][K2][K3][K4];

function dig<
  T,
  K1 extends keyof T,
  K2 extends keyof T[K1],
  K3 extends keyof T[K1][K2],
  K4 extends keyof T[K1][K2][K3],
  K5 extends keyof T[K1][K2][K3][K4]
  >(obj: T, key1: K1, key2: K2, key3: K3, key4: K4, key5: K5): T[K1][K2][K3][K4][K5];

function dig<
  T,
  K1 extends keyof T,
  K2 extends keyof T[K1],
  K3 extends keyof T[K1][K2],
  K4 extends keyof T[K1][K2][K3],
  K5 extends keyof T[K1][K2][K3][K4]
  >(obj: T, key1: K1, key2?: K2, key3?: K3, key4?: K4, key5?: K5):
  T[K1] |
  T[K1][K2] |
  T[K1][K2][K3] |
  T[K1][K2][K3][K4] |
  T[K1][K2][K3][K4][K5] {
    let value: any = obj && obj[key1];

    if (key2) {
      value = value && value[key2];
    }

    if (key3) {
      value = value && value[key3];
    }

    if (key4) {
      value = value && value[key4];
    }

    if (key5) {
      value = value && value[key5];
    }

    return value;
}

Beispiel auf einem Spielplatz .

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.