Wpisz na maszynie bezpieczny operator nawigacji (?.) Lub (!.) I ścieżki właściwości o wartości null


111

W szablonach Angular2 operator bezpieczny ?.działa, ale nie w przypadku component.tsużywania języka TypeScript 2.0. Również operator bezpiecznej nawigacji (!.) Nie działa.

Na przykład...

Ten TypeScript

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

kompiluje się do tego JavaScript

if (a.b.c) { }

Ale kiedy go uruchamiam, pojawia się następujący błąd:

Nie można odczytać właściwości „b” wartości undefined

Czy jest jakaś alternatywa dla poniższych?

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

7
Operatory maszynopisu istnieją tylko do kompilacji, nie ma ich w skompilowanym javascript. Opublikowany przez Ciebie błąd to błąd czasu wykonywania.
— Nitzan Tomer

o (o (o (o (test) .level1) .level2) .level3 or o (o (o (o (a) .b) .c) .d stackoverflow.com/a/35169378/3914072 to rozwiązanie działa świetnie dla nas w czasie kompilacji i to z bezpiecznymi typami
— Rajab Shakirov

Odpowiedzi:


74

Od czasu wydania TypeScript 3.7 można teraz używać opcjonalnego tworzenia łańcuchów.

Przykład nieruchomości:

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

Jest to równoważne z:

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

Ponadto możesz zadzwonić:

Połączenie opcjonalne

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

otherFn zostanie wywołane tylko wtedy, gdy otherFn nie będzie równe null lub undefined

Użycie opcjonalnego łączenia łańcuchowego w instrukcji IF

To:

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

można teraz zastąpić tym

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

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


to ciągle generuje błąd oczekiwany przez Expression podczas kompilacji, działa, jeśli użyję !zamiast ?w "maszynopisie": "~ 3.5.3"
— Scaramouche

3
@Scaramouche dzieje się tak, ponieważ opcjonalny operator łańcuchowy ( ?.) został wprowadzony w 3.7, a ty używasz 3.5.3. W wersjach wcześniejszych niż 3.7, TypeScript zakłada, że ?jest używany w skrócie if form ( foo ? bar : baz), a więc oczekuje reszty wyrażenia
— George

134

!jest niezerowym operatorem asercji (wyrażenie po poprawce) - po prostu mówi do wpisania checker, że jesteś pewien, że anie jest nulllub undefined.

operacja a!daje wartość typu az nulli undefinedwykluczona


Opcjonalnie łańcuchowym wreszcie wykonany mu maszynopis ( 3.7 ) 🎉

Opcjonalny operator łańcuchowy ?. umożliwia odczytanie wartości właściwości znajdującej się głęboko w łańcuchu połączonych obiektów bez konieczności wyraźnego sprawdzania poprawności każdego odwołania w łańcuchu. Te ?.funkcje podobne do obsługi .operatora CHAINING, z wyjątkiem, że zamiast powoduje błąd jeśli odniesienia nullish ( nulli undefined), przy czym zwiera ekspresji o wartości powrotnej undefined. W przypadku użycia z wywołaniami funkcji zwraca, undefinedjeśli podana funkcja nie istnieje.

Składnia :

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

Zwróć uwagę :

Opcjonalne łączenie łańcuchowe nie jest ważne po lewej stronie przydziału

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

12
Dla zainteresowanych sprawdzeniem statusu tego. TypeScript planuje dodanie go, gdy ECMAScript doda obsługę. ECMAScript śledzi ten problem tutaj .
— Tempo

o (o (o (o (test) .level1) .level2) .level3 or o (o (o (o (a) .b) .c) .d stackoverflow.com/a/35169378/3914072 to rozwiązanie działa świetnie dla nas w czasie kompilacji i to z bezpiecznymi typami
— Rajab Shakirov

plac zabaw dla dzieci z nowej funkcji (ukaże się 5 listopada): typescriptlang.org/play/?ts=3.7.0-pr-33294-11#code/...
— Nisim Józefa

1
@ aleksey-l: Może nie być złym pomysłem przekreślenie zawartości przed „ostatnią aktualizacją”. Pomoże ludziom, którzy nie czytają od góry do dołu (czyli za szybko)! :)
— Aidin

1
To takie fajne - nie miałem pojęcia, że ​​można go użyć do wywołań funkcji. Szukałem rozwiązania i znalazłem Twoją odpowiedź, więc dziękuję!
— Geoff Davids

22

Aktualizacja:

Planowane w ramach wersji 3.7
https://github.com/microsoft/TypeScript/issues/33352


Możesz spróbować napisać taką funkcję niestandardową.

Główną zaletą tego podejścia jest sprawdzanie typu i częściowa inteligencja.

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

I użycie (obsługuje do 5 parametrów i można je rozszerzyć):

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

Przykład na placu zabaw .


1
Działa to dobrze, ale ma problem - VSCode nie pokaże tego, gdy użyjesz "Znajdź wszystkie odwołania" na jednym z klawiszy.
— Ivan Koshelev

Jaki rodzaj sprawdzania typu? W momencie, gdy przekazujesz stringi jako parametry, tracisz typ. Jeśli tak, lepiej po prostu użyj lodash.get
— un33k

16

Inną alternatywą wykorzystującą zewnętrzną bibliotekę jest _.has () z Lodash .

Na przykład

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

jest równe

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

EDYCJA: Jak zauważono w komentarzach, podczas korzystania z tej metody tracisz możliwość wnioskowania o typie Typescript. Np. Zakładając, że czyjeś obiekty są poprawnie wpisane, otrzymamy błąd kompilacji z (a && ab && abz), jeśli z nie jest zdefiniowane jako pole obiektu b. Ale używając _.has (a, 'b.z'), nie można uzyskać tego błędu.


Dobra wskazówka, chociaż żadne inne rozwiązanie nie jest dostępne, wydaje się, że jest to dobra alternatywa.
— Dimas Crocco

3
Ale jak to działa z wnioskiem o typie TypeScript?
— VitalyB

@VitalyB prawdopodobnie masz na myśli importowanie odpowiedniego interfejsu lodash. (import * as _ z 'lodash';)
— Ankur Arora

1
@AnkurArora, myślę, że VitalyB ma na myśli to, że żadna kontrola typu nie jest wykonywana przy użyciu metody Lodash. Np. Zakładając, że czyjeś obiekty są poprawnie wpisane, otrzymamy błąd kompilacji z (a && ab && abz), jeśli z nie jest zdefiniowane jako pole obiektu b. Ale używając _.has (a, 'b.z'), nie można uzyskać tego błędu. Jeśli chodzi o oświadczenie importu, tak, jest to zdecydowanie potrzebne.
— TiaanM

1
@TiaanM Dokładnie to miałem na myśli. Właśnie znalazłem nową bibliotekę, która obejmuje problem z typami: stackoverflow.com/a/55462483/126574
— VitalyB

3

Nowa biblioteka o nazwie ts-optchain zapewnia tę funkcjonalność i, w przeciwieństwie do rozwiązania lodash, zapewnia również bezpieczeństwo twoich typów, oto próbka jej użycia (zaczerpnięta z pliku readme):

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

Opierając się na odpowiedzi @ Pvl, możesz uwzględnić bezpieczeństwo typów w zwracanej wartości, jeśli używasz nadpisań:

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

Przykład na placu zabaw .

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.