Czy maszynopis obsługuje? operator? (A jak to się nazywa?)


336

Czy maszynopis obecnie (lub czy istnieją plany) obsługi operatora bezpiecznego systemu nawigacji?.

to znaczy:

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

Jest też bardziej popularna nazwa tego operatora (trudno google znaleźć).


3
@ mattytommo masz to w c #, nazywa się on zerowym operatorem koalescencji i używa ?? składnia weblogs.asp.net/scottgu/archive/2007/09/20/…
basarat

2
@BasaratAli Niestety nie, łączenie jest w porządku property ?? property2, ale gdybyś próbował property.company ?? property1.companyi propertybył zerowy, dostanieszNullReferenceException
mattytommo

1
@mattytommo Dzięki, rozumiem teraz? faktycznie moczy wszystkie zerowe odwołania w łańcuchu. Słodkie.
basarat

9
@mattytommo to już istnieje dla C #: msdn.microsoft.com/en-us/library/dn986595.aspx
Highmastdon

9
Przedstawiciel firmy Microsoft, który nas odwiedził, nazwał go operatorem Elvisa, ponieważ znak zapytania wygląda jak włosy Elvisa i mikrofon, w którym śpiewa ...
Zymotik

Odpowiedzi:


167

Aktualizacja : jest obsługiwany od TypeScript 3.7 i nazywa się Opcjonalne tworzenie łańcuchów : https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#optional-chaining

Nie mogę znaleźć żadnego odniesienia do tego w specyfikacji języka TypeScript .

Jeśli chodzi o to, co wywołać tego operatora w CoffeeScript, nazywa się to operatorem egzystencjalnym (w szczególności „wariantem akcesorium” operatora egzystencjalnego).

Z dokumentacji CoffeeScript na temat operatorów :

Wariant akcesorium operatora egzystencjalnego ?.może być wykorzystany do wchłonięcia zerowych referencji w łańcuchu właściwości. Użyj go zamiast akcesorium kropkowego .w przypadkach, gdy wartość podstawowa może być zerowa lub niezdefiniowana .

Zatem wariant akcesorium operatora egzystencjalnego wydaje się właściwym sposobem na odniesienie się do tego operatora; i wydaje się, że TypeScript obecnie go nie obsługuje (chociaż inni wyrazili chęć takiej funkcjonalności ).


28
„wariant akcesorium operatora egzystencjalnego”. Naturalnie. Tak chwytliwe, że prawie niemożliwe jest zapomnienie. :) Dzięki za niezwykle dokładną odpowiedź.
Marty Pitt,

1
@MartyPitt Pewnie! Zgadzam się, chciałbym zobaczyć a) szersze przyjęcie takiego operatora (C # proszę!) Ib) lepszą nazwę (operator „bezpiecznej nawigacji” z twojego linku na blogu ma fajny dźwięk).
Donut

2
Angular implementuje to w swoich szablonach: angular.io/guide/…
Enzoaeneas

5
W niektórych innych językach nazywany jest operatorem „Elvis”
k0enf0rNL

4
Zostało ogłoszone dla TypeScript 3.7.0 ( github.com/microsoft/TypeScript/issues/… )
c_froehlich,

146

Nie tak fajny jak singiel?, Ale działa:

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

Możesz użyć dowolnej liczby &&:

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

33
&& ocenia, o ile stwierdzenie jest prawdziwe. Jeśli to prawda, zwraca ostatnią wartość. Jeśli jest fałszem, zwraca pierwszą wartość, która ma wartość false. Może to być 0, zero, fałsz itp. || zwraca pierwszą wartość, która ma wartość true.
A. KR

34
Nie działa dobrze, jeśli pasek jest zdefiniowany, ale zwraca wartość false (np. Boolean false lub zero).
mt_serg

96

Jest to zdefiniowane w specyfikacji opcjonalnego łańcucha ECMAScript, więc prawdopodobnie powinniśmy odwoływać się do opcjonalnego łańcucha podczas omawiania tego. Prawdopodobne wdrożenie:

const result = a?.b?.c;

Krótko mówiąc o tym, że zespół TypeScript czeka na doprecyzowanie specyfikacji ECMAScript, więc ich implementacja może być niezłomna w przyszłości. Gdyby coś zaimplementowali teraz, skończyłoby się to poważnymi zmianami, gdyby ECMAScript przedefiniował ich specyfikację.

Zobacz opcjonalną specyfikację łączenia

Tam, gdzie coś nigdy nie będzie standardowym skryptem JavaScript, zespół TypeScript może wdrożyć, co uzna za stosowne, ale w przypadku przyszłych dodatków ECMAScript chcą zachować semantykę, nawet jeśli dają wczesny dostęp, tak jak w przypadku wielu innych funkcji.

Short Cuts

Dostępne są więc wszystkie operatory JavaScript w funky, w tym konwersje typów, takie jak ...

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

Rozwiązanie ręczne

Ale wracając do pytania. Mam tępy przykład tego, jak możesz zrobić podobną rzecz w JavaScript (i dlatego TypeScript), chociaż zdecydowanie nie sugeruję, że jest to wdzięczne, ponieważ funkcja, której naprawdę szukasz.

(foo||{}).bar;

Więc jeśli footo undefinedwynik jest undefinedi jeśli foojest zdefiniowany i ma właściwość o nazwie bar, która ma wartość, wynik jest, że wartość.

I umieścić przykład na JSFiddle .

Wygląda to dość szkicowo w przypadku dłuższych przykładów.

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

Funkcja łańcucha

Jeśli desperacko oczekujesz krótszej wersji, gdy specyfikacja wciąż jest w powietrzu, w niektórych przypadkach używam tej metody. Ocenia wyrażenie i zwraca wartość domyślną, jeśli łańcuch nie może być spełniony lub kończy się na wartości zerowej / niezdefiniowanej (zwróć uwagę, że !=tutaj jest to ważne, nie chcemy używać, !==ponieważ chcemy tutaj trochę pozytywnego żonglowania).

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
ciekawe, ale w moim przypadku (this.loop || {}).nativeElementpowiedzenie Property 'nativeElement' does not exist on type '{}'. any this.looptypeof angular.io/api/core/ElementRef
kuncevic.dev

@Kuncevic - musisz albo ... 1) podać zgodne ustawienie domyślne zamiast {}, lub 2) użyć asercji typu, aby wyciszyć kompilator.
Fenton,

Zakładając, że foojest to rzeczywiście przydatny obiekt: (foo || {}).bargeneralnie nie będzie się kompilował w maszynopisie, ponieważ {}nie będzie tego samego typu co foo. Właśnie tego problemu @ VeganHunter chce uniknąć.
Simon_Weaver,

1
@ Simon_Weaver to (foo || {bar}). Bar pozwoli kompilatorowi działać płynnie i myślę, że gadatliwość jest do przyjęcia.
Harps

@harps faktycznie kompiluje się tylko wtedy, gdy bar jest zdefiniowany jako zmienna, co najprawdopodobniej nie byłoby
Simon_Weaver

82

Aktualizacja: Tak, teraz jest obsługiwany!

Właśnie został wydany z TypeScript 3.7: https://devblogs.microsoft.com/typescript/announcing-typescript-3-7/

Nazywa się to opcjonalnym łańcuchem : https://devblogs.microsoft.com/typescript/announcing-typescript-3-7/#optional-chaining

Dzięki temu:

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

jest równa:

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

Stara odpowiedź

Na github jest otwarta prośba o tę funkcję, gdzie możesz wyrazić swoją opinię / pragnienie: https://github.com/Microsoft/TypeScript/issues/16


36

Edytuj 13 listopada 2019!

Od 5 listopada 2019 r. Dostarczono TypeScript 3.7, który obsługuje ?. opcjonalnego operatora łączenia aining !!!

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


Tylko do celów historycznych:

Edytuj: Zaktualizowałem odpowiedź dzięki komentarzowi Fracza.

Wydany TypeScript 2.0 !.To nie to samo co ?.(Safe Navigator w C #)

Zobacz tę odpowiedź, aby uzyskać więcej informacji:

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

To tylko powie kompilatorowi, że wartość nie jest pusta ani niezdefiniowana. To nie sprawdzi, czy wartość jest pusta czy niezdefiniowana.

Operator asercji typu 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
Nie to samo, ponieważ ?ponieważ zapewnia, że wartość jest zdefiniowana. ?oczekuje się, że po cichu zawiedzie / oceni się na fałsz. W każdym razie dobrze wiedzieć.
fracz

1
Teraz, gdy o tym myślę ... Ta odpowiedź jest dość bezcelowa, ponieważ nie wykonuje „bezpiecznej nawigacji”, jaką robi operator C #.
Jose A

5
To jednak odpowiedziało na moje pytanie. Wiedziałem o? z c # i wypróbowałem to w maszynopisie. Nie działało, ale to widziałem! istniał, ale nie wiedział, co zrobił. Zastanawiałem się, czy to jest to samo, przeszukałem google i znalazłem drogę do tego pytania, które poinformowało mnie, że nie, są różne.
Llewey 27.04.17

11

Opcjonalny operator łańcuchowy Elvis (?.) Jest obsługiwany w TypeScript 3.7.

Możesz go użyć do sprawdzenia wartości null: cats?.miowszwraca null, jeśli koty są null lub niezdefiniowane.

Możesz go również użyć do opcjonalnego wywoływania metod: cats.doMiow?.(5)wywoła doMiow, jeśli istnieje.

Dostęp nieruchomość jest również możliwe: cats?.['miows'].

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


Proszę mnie poprawić, ale operator Elvisa jest przynajmniej w Kotlinie ?:. Czy masz referencje?
rekire


1
Wkrótce będzie obsługiwany w zwykłym JS - developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Mottie

1
Zapowiedź wydania TypeScript 3.7 wspomina o nim: devblogs.microsoft.com/typescript/announcing-typescript-3-7
György Balássy

10

Operator ?.nie jest obsługiwany w wersji 2.0 .

Używam więc następującej funkcji:

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

użycie wygląda następująco:

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

ponadto możesz ustawić wartość domyślną:

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

Działa naprawdę dobrze z IntelliSense w Visual Studio.


1
Właśnie tego szukałem! Działa w maszynopisie 2.1.6.
Rajab Shakirov

5
lub możesz to nazwać elvis<T>;-)
Simon_Weaver

3
Simon_Weaver, nazywam to „smutnym klaunem”: o (
VeganHunter,

5

W końcu jest tutaj!

Oto kilka przykładów:

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

Ale to nie działa dokładnie tak, jak twoje założenie.

Zamiast oceny

foo?.bar

do tego małego fragmentu kodu wszyscy jesteśmy przyzwyczajeni do pisania

foo ? foo.bar : null

faktycznie ocenia

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

który działa dla wszystkich wartości falsey, takich jak pusty ciąg, 0 lub fałsz.

Po prostu nie mam wyjaśnienia, dlaczego go nie kompilują foo == null


3

Stworzyliśmy tę metodę wykorzystania podczas pracy na Phonetradr, która umożliwia bezpieczny dostęp do głębokich właściwości za pomocą 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);


Fajne!! Czy są jakieś zastrzeżenia? Mam klasę otoki z około 20 pobierającymi do napisania, każdy z nich ma następujący typ zwrotu - i wszystkie pola muszą być sprawdzone return this.entry.fields.featuredImage.fields.file.url;
zerowo

Jedynym zastrzeżeniem może być wpływ na wydajność, ale nie mam uprawnień, aby mówić o tym, jak radzą sobie z tym różne zespoły JIT.
Ray Suelzer

2

Zasadniczo nie zalecam tego podejścia (uważaj na problemy z wydajnością), ale możesz użyć operatora rozprzestrzeniania, aby płytko sklonować obiekt, na którym możesz następnie uzyskać dostęp do właściwości.

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

Działa to, ponieważ typ „firstName” jest „propagowany” przez.

Użyję tego najczęściej, gdy mam find(...)wyrażenie, które może zwrócić null i potrzebuję z niego jednej właściwości:

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

Mogą istnieć pewne przypadki krawędzi ze sposobem, w jaki typ maszynowy określa typy, i to się nie kompiluje, ale ogólnie powinno to działać.


2

Nazywa się to opcjonalnym łańcuchem i znajduje się w maszynowym rozdziale 3.7

Opcjonalne tworzenie łańcuchów pozwala nam pisać kod, w którym możemy natychmiast przestać uruchamiać niektóre wyrażenia, jeśli napotkamy wartość null lub niezdefiniowaną


0

Jak już wcześniej wspomniano, wciąż jest rozważany, ale od kilku lat jest martwy w wodzie .

Opierając się na istniejących odpowiedziach, oto najbardziej zwięzła wersja podręcznika, jaką mogę wymyślić:

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'

Po prostu dyskretnie zawodzi w przypadku brakujących błędów właściwości. Wraca do standardowej składni do określania wartości domyślnej, którą można również całkowicie pominąć.


Chociaż działa to w prostych przypadkach, jeśli potrzebujesz bardziej skomplikowanych rzeczy, takich jak wywołanie funkcji, a następnie uzyskanie dostępu do właściwości wyniku, wszystkie inne błędy również zostaną połknięte. Zły projekt.

W powyższym przypadku zoptymalizowana wersja drugiej odpowiedzi zamieszczonej tutaj jest lepszą opcją:

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'

Bardziej złożony przykład:

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

Możesz także przejść w drugą stronę i użyć czegoś takiego jak Lodash ” _.get(). Jest zwięzły, ale kompilator nie będzie w stanie ocenić poprawności użytych właściwości:

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

0

Jeszcze nie (we wrześniu 2019 r.), Ale ponieważ „bezpieczny operator nawigacji” znajduje się obecnie na etapie 3 , jest on implementowany w TypeScript.

Obejrzyj ten problem, aby uzyskać aktualizacje:

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

Kilka silników ma wczesne wdrożenia:

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

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

Możesz teraz zainstalować wtyczkę, aby ją obsługiwać:

npm install --save-dev ts-optchain

W twoim pliku tsconfig.json:

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

Oczekuję, że ta odpowiedź będzie nieaktualna w ciągu najbliższych 6 miesięcy, ale mam nadzieję, że w międzyczasie komuś pomoże.


-1

_.get(obj, 'address.street.name')działa świetnie dla JavaScript, gdzie nie masz żadnych typów. Ale do TypeScript potrzebujemy prawdziwego operatora Elvisa!

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.