Чи підтримує Typescript ?. оператор? (І, як це називається?)


336

Чи підтримує Typescript на даний момент (чи планують це) підтримувати безпечний навігаційний оператор?.

тобто:

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

Крім того, чи є більш поширене ім'я цього оператора (його незрозуміло важко).


3
@mattytommo у вас є це в c #, його називають оператором null coalescing і використовує ?? синтаксис weblogs.asp.net/scottgu/archive/2007/09/20/…
basarat

2
@BasaratAli На жаль, ні, злиття чудово підходить property ?? property2, але якби ви спробували property.company ?? property1.companyі propertyбуло недійсним, ви отримаєтеNullReferenceException
mattytommo

1
@mattytommo Дякую, я зараз це отримаю "?". насправді замочує всі нульові посилання в ланцюзі. Солодке.
Басарат

9
@mattytommo це існує зараз для C #: msdn.microsoft.com/en-us/library/dn986595.aspx
Highmastdon

9
Представник Microsoft, який відвідав нас, назвав його оператором Елвіса, оскільки питання питання схоже на волосся Елвіса та мікрофон, на який він співає ...
Zymotik

Відповіді:


167

Оновлення : підтримується як TypeScript 3.7, так і називається Необов’язкове ланцюжок : https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#optional-chaining

Я не можу знайти жодної посилання на нього в специфікації мови TypeScript .

Що стосується того, як називати цього оператора в CoffeeScript, він називається екзистенціальним оператором (конкретно, "варіантом аксесуара" екзистенціального оператора).

З документації CoffeeScript про операторів :

Варіант аксесуара екзистенціального оператора ?.може використовуватися для всмоктування нульових посилань у ланцюжку властивостей. Використовуйте його замість точкового аксесуара .у випадках, коли базове значення може бути нульовим або невизначеним .

Отже, варіант аксесуара екзистенційного оператора є правильним способом звернення до цього оператора; і видається, що TypeScript на даний момент не підтримує його (хоча інші виявили бажання цієї функції ).


28
"варіант доступу екзистенційного оператора". Природно. Так привабливий, що забути майже неможливо. :). Дякую за надзвичайно ретельну відповідь.
Марті Пітт

1
@MartyPitt Звичайно, річ! Я погоджуюсь, я хотів би побачити: а) ширше прийняття такого оператора (C # будь ласка!) І б) кращого імені (оператор "безпечної навігації" з вашого пов’язаного блогу має приємне дзвінок).
Пончик

2
Кутова реалізує це у своїх шаблонах: angular.io/guide/…
Enzoaeneas

5
В деяких інших мовах його називають оператором "Елвіс"
k0enf0rNL,

4
Це оголошено для TypeScript 3.7.0 ( github.com/microsoft/TypeScript/isissue/… )
c_froehlich

146

Не так добре, як сингл ?, але це працює:

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

Ви можете використовувати стільки &&, скільки вам подобається:

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

33
&& оцінює, доки твердження є правдивим. Якщо це правда, вона повертає останнє значення. Якщо вона помилкова, вона повертає перше значення, яке оцінюється, на помилкове. Це може бути 0, null, false тощо || повертає перше значення, яке оцінюється, на істинне.
А. KR

34
Не працює добре, якщо смужка визначена, але вона оцінюється як хибна (як булева помилка чи нуль).
mt_serg

96

Це визначено в специфікації додаткового ланцюга ECMAScript, тому ми, мабуть, повинні посилатися на необов'язкові ланцюжки, коли ми обговорюємо це. Ймовірна реалізація:

const result = a?.b?.c;

Довгий і короткий у цьому випадку полягає в тому, що команда TypeScript чекає посилення специфікації ECMAScript, тому в майбутньому їх реалізація може бути непорушною. Якщо вони щось реалізували зараз, вони, в кінцевому рахунку, потребують великих змін, якщо ECMAScript перегляне їх специфікацію.

Див. Додаткову специфікацію ланцюга

Якщо щось ніколи не буде стандартним JavaScript, команда TypeScript може реалізовувати, як вважає за потрібне, але для майбутніх доповнень ECMAScript вони хочуть зберегти семантику, навіть якщо вони надають ранній доступ, як у них для багатьох інших функцій.

Короткі вирізи

Таким чином, всі функціональні оператори JavaScripts доступні, включаючи перетворення типу, такі як ...

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

Ручне рішення

Але повернемось до питання. У мене є чіткий приклад того, як ви можете робити подібну річ в JavaScript (і, отже, TypeScript), хоча я точно не припускаю, що це витончена функція, яку ви справді шукаєте.

(foo||{}).bar;

Так що, якщо fooє undefinedрезультат , undefinedі якщо fooвизначений і має ім'я властивістьbar , який має значення, в результаті чого значення.

Я ставлю приклад на JSFiddle .

Це виглядає досить схематично для довших прикладів.

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

Функція ланцюга

Якщо ви відчайдушно ставитеся до коротшої версії, поки специфікація все ще в повітрі, я використовую цей метод у деяких випадках. Він оцінює вираз і повертає за замовчуванням, якщо ланцюг не може бути задоволений або закінчується нульовим / невизначеним (зауважте, що !=тут важливо, ми не хочемо використовувати, !==оскільки тут хочеться трохи позитивного жонглювання).

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
цікаво, але в моєму випадку (this.loop || {}).nativeElementговорять Property 'nativeElement' does not exist on type '{}'. any this.looptypeof angular.io/api/core/ElementRef
kuncevic.dev

@Kuncevic - вам потрібно або 1) надати сумісний за замовчуванням замість {}, або 2) використовувати твердження типу, щоб заглушити компілятор.
Фентон

Припустимо foo, що це фактично корисний об’єкт: (foo || {}).barзазвичай не збирається компілювати в typecript, оскільки {}він не буде того ж типу, що і foo. Це проблема, яку вирішує @ VeganHunter.
Simon_Weaver

1
@Simon_Weaver тоді (foo || {bar}). Бар дозволить компілятору працювати безперебійно, і я вважаю, що багатослівність прийнятна.
Арфи

@harps насправді це компілюється лише в тому випадку, якщо смужка визначена як змінна, що, швидше за все, не буде
Simon_Weaver

82

Оновлення: Так, його зараз підтримується!

Він щойно вийшов з TypeScript 3.7: https://devblogs.microsoft.com/typescript/announcing-typescript-3-7/

Це називається необов'язковим ланцюжком : https://devblogs.microsoft.com/typescript/announcing-typescript-3-7/#optional-chaining

З ним наступне:

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

еквівалентно:

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

Стара відповідь

Для цього на Github є запит на відкриту функцію, де ви можете озвучити свою думку / бажання: https://github.com/Microsoft/TypeScript/isissue/16


36

Редагувати 13 листопада 2019 року!

З 5 листопада 2019 року TypeScript 3.7 відвантажений, і тепер він підтримує?. опціональний оператор ланцюга 🎉🎉🍾🍾🎉 !!!

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


Тільки для історичних цілей:

Редагувати: Я оновив відповідь завдяки коментарю fracz.

Випущений TypeScript 2.0 !.Це не те саме, що ?.(Безпечний навігатор у C #)

Дивіться цю відповідь для отримання більш детальної інформації:

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

Це скаже лише компілятору, що значення не є нульовим або невизначеним. Цього не буде перевірить, чи значення є нульовим чи невизначеним.

Оператор твердження ненульового типу TypeScript

// 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
Не те саме, що ?тому, що воно стверджує, що значення визначене. ?Очікується, що мовчки відмовлять / оцінюватимуть як хибне. У всякому разі, добре знати.
fracz

1
Тепер, коли я замислююся над цим ... Ця відповідь є досить безглуздою, оскільки вона не робить "безпечної навігації", що робить оператор C #.
Хосе А

5
Однак це відповіло на моє запитання. Я про це знав ?. від c # і спробував це в машинописі. Це не спрацювало, але я це побачив! існували, але не знали, що це зробило. Я поцікавився, чи не те саме, здійснив пошук в Google і знайшов дорогу до цього питання, яке повідомило мені, що ні, вони різні.
Llewey

11

Оператор додаткового ланцюга Elvis (?.) Підтримується в TypeScript 3.7.

Ви можете використовувати його для перевірки нульових значень: cats?.miows повертає null, якщо коти є нульовими або невизначеними.

Ви також можете використовувати його для необов'язкового виклику методу: cats.doMiow?.(5) буде викликати doMiow, якщо він існує.

Доступ до нерухомості також можливо: cats?.['miows'].

Довідка: https://devblogs.microsoft.com/typescript/announcing-typescript-3-7-beta/


Виправте мене, але оператор Елвіса є принаймні в Котліні ?:. У вас є довідник?
rekire


1
Незабаром він буде підтримуватися у звичайному JS - developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Mottie

1
У повідомленні про випуск TypeScript 3.7 згадується це: devblogs.microsoft.com/typescript/announcing-typescript-3-7
György Balássy

10

Оператор ?. не підтримується у TypeScript версії 2.0 .

Тому я використовую таку функцію:

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

використання виглядає приблизно так:

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

плюс, ви можете встановити значення за замовчуванням:

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

Це дуже добре працює з IntelliSense у Visual Studio.


1
Це саме те, що я шукав! Він працює в машинописі 2.1.6.
Раджаб Шакіров

5
або ви можете назвати це elvis<T>;-)
Simon_Weaver

3
Simon_Weaver, я називаю це "сумним клоуном": o (
VeganHunter

5

Це нарешті тут!

Ось кілька прикладів:

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

Але це не працює точно так само, як ваше припущення.

Замість оцінки

foo?.bar

до цього маленького фрагмента коду ми всі звикли писати

foo ? foo.bar : null

він насправді оцінює до

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

яка працює для всіх значень фальси, як порожній рядок, 0 або false.

У мене просто немає пояснень, чому вони не складають його foo == null


3

Цей метод утиліти ми створили під час роботи над Phonetradr, яка може надати безпечний доступ до глибоких властивостей за допомогою 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);


Класно !! Чи є якісь застереження? У мене є клас обгортки з 20 записами, щоб написати, кожен з них має такий тип повернення - і всі поля повинні бути перевірені на нульreturn this.entry.fields.featuredImage.fields.file.url;
Drenai

Єдиний застереження, можливо, може мати вплив на ефективність, але я не кваліфікований, щоб говорити про те, як різні JITers впораються з цим.
Рей Суельцер

2

Я зазвичай не рекомендую такий підхід (слідкуйте за занепокоєннями щодо продуктивності), але ви можете використовувати оператор розповсюдження, щоб дрібно клонувати об’єкт, до якого потім можна отримати доступ до ресурсу.

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

Це працює, тому що тип "firstName" "поширюється" наскрізь.

Я буду використовувати це найчастіше, коли у мене є find(...)вираз, який може повернути нуль, і мені потрібне одне властивість з нього:

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

Можливо, є кілька кращих випадків із тим, як typecript виводить типи, і це не компілюється, але це, як правило, має працювати.


2

Це називається факультативним ланцюжком і знаходиться в Typescript 3.7

Необов'язковий ланцюжок дозволяє нам писати код, де ми можемо негайно припинити виконання деяких виразів, якщо зіткнутись з нульовим або невизначеним


0

Як було сказано раніше, наразі це все ще розглядається, але воно було мертвим у воді вже кілька років.

Спираючись на наявні відповіді, ось найповніший посібник варіант, про який я можу придумати:

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'

Він просто безшумно відмовляється від відсутніх помилок власності. Він повертається до стандартного синтаксису для визначення значення за замовчуванням, яке також можна повністю опустити.


Хоча це працює для простих випадків, якщо вам потрібні складніші речі, такі як виклик функції, а потім доступ до властивості за результатом, то будь-які інші помилки також проковтуються. Поганий дизайн.

У наведеному вище випадку кращим варіантом є оптимізована версія іншої відповіді, розміщеної тут.

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'

Більш складний приклад:

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

Ви також можете піти іншим шляхом і скористатися чимось на зразок Лодаша _.get(). Це стисло, але компілятор не зможе оцінити обґрунтованість використовуваних властивостей:

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

0

Ще не (станом на вересень 2019 року), але оскільки "оператор безпечної навігації" зараз знаходиться на 3 етапі , він реалізується в TypeScript.

Перегляньте цю проблему для оновлень:

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

Кілька двигунів мають ранню реалізацію:

ВАТ: https://bugs.webkit.org/show_bug.cgi?id=200199

V8: https://bugs.chromium.org/p/v8/isissue/detail?id=9553

SM: https://bugzilla.mozilla.org/show_bug.cgi?id=1566143

(через https://github.com/tc39/proposition-optional-chaining/isissue/115#issue-475422578 )

Ви можете встановити плагін, щоб підтримати його зараз:

npm install --save-dev ts-optchain

У вашому tsconfig.json:

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

Я очікую, що ця відповідь застаріла протягом наступних 6 місяців, але, сподіваємось, вона тим часом допоможе комусь.


-1

_.get(obj, 'address.street.name')відмінно підходить для JavaScript, де у вас немає типів. Але для TypeScript нам потрібен справжній оператор Elvis!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.