Typescript поддерживает? оператор? (И как это называется?)


337

Поддерживает ли Typescript в настоящее время (или планирует ли) оператора безопасной навигации?.

то есть:

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

Кроме того, есть более распространенное имя для этого оператора (это невероятно трудно для Google).


3
@mattytommo у вас есть это в C #, он называется оператором объединения нулей и использует ?? синтаксис weblogs.asp.net/scottgu/archive/2007/09/20/…
Басарат

2
@BasaratAli К сожалению, нет, coalesce подходит для этого 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

Ответы:


168

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

Я не могу найти ссылку на это вообще в спецификации языка TypeScript .

Что касается того, что называть этим оператором в CoffeeScript, он называется экзистенциальным оператором (в частности, «вариантом доступа» экзистенциального оператора).

Из документации CoffeeScript по операторам :

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

Таким образом, вариант доступа экзистенциального оператора представляется правильным способом обращения к этому оператору; и TypeScript в настоящее время, кажется, не поддерживает его (хотя другие выразили желание этой функциональности ).


28
msgstr "вариант доступа экзистенциального оператора". Естественно. Так броско, что почти невозможно забыть. :). Спасибо за очень тщательный ответ.
Марти Питт

1
@MartyPitt Конечно! Я согласен, я хотел бы видеть а) более широкое принятие такого оператора (C #, пожалуйста!) И б) более подходящее имя (у оператора "безопасной навигации" из вашего связанного поста в блоге есть приятное звучание).
Пончик

2
Angular реализует это в своих шаблонах: angular.io/guide/…
Enzoaeneas

5
На некоторых других языках его называют оператором «Элвиса»
k0enf0rNL

4
Это объявлено для TypeScript 3.7.0 ( github.com/microsoft/TypeScript/issues/… )
c_froehlich

146

Не так хорошо, как сингл ?, но это работает:

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

Вы можете использовать столько &&, сколько хотите:

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

33
&& оценивает, пока утверждение верно. Если это правда, он возвращает последнее значение. Если это false, он возвращает первое значение, которое оценивается как false. Это может быть 0, ноль, ложь и т. Д. || возвращает первое значение, которое оценивается как true.
А.Р.

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;

Функция цепи

Если вы отчаянно нуждаетесь в более короткой версии, пока спецификация находится в стадии разработки, в некоторых случаях я использую этот метод. Он вычисляет выражение и возвращает значение по умолчанию, если цепочка не может быть удовлетворена или заканчивается null / undefined (обратите внимание, что !=здесь важно, мы не хотим использовать, так !==как мы хотим немного положительного жонглирования здесь).

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обычно не собирается компилироваться в машинописи, потому {}что не будет того же типа, что и foo. Это проблема, которую решение @ VeganHunter стремится избежать.
Simon_Weaver

1
@Simon_Weaver then (foo || {bar}). Bar позволит компилятору работать без сбоев, и я думаю, что многословие приемлемо.
Арфы

@harps на самом деле это компилируется, только если bar определен как переменная, чего, скорее всего, не будет
Simon_Weaver

83

Обновление: да, сейчас поддерживается!

Он был выпущен только с 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/issues/16


36

Изменить 13 ноября 2019 года!

По состоянию на 5 ноября 2019 года поставляется TypeScript 3.7, и теперь он поддерживает?. дополнительный оператор объединения cha !!!

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


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

Изменить: я обновил ответ благодаря комментарию Fracz.

Выпущен TypeScript 2.0 !.Это не то же самое, что ?.(Safe Navigator в 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

Дополнительный оператор цепочки Элвиса (?.) Поддерживается в TypeScript 3.7.

Вы можете использовать его для проверки на нулевые значения: cats?.miowsвозвращает ноль, если коты имеют нулевое значение или не определены.

Вы также можете использовать его для необязательного вызова метода: cats.doMiow?.(5)вызовет doMiow, если он существует.

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

Ссылка: https://devblogs.microsoft.com/typescript/announcing-typescript-3-7-beta/


Пожалуйста, поправьте меня, но оператор Элвиса находится по крайней мере в Котлине ?:. У вас есть ссылка?
19


1
Это будет поддерживаться в простом JS в ближайшее время - developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Mottie

1
Объявление о выпуске TypeScript 3.7 упоминает это: devblogs.microsoft.com/typescript/announcing-typescript-3-7
Дьёрдь Баласси,

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

который работает для всех значений Falsey, таких как пустая строка, 0 или false.

У меня просто нет объяснения, почему они не компилируют это foo == null


3

Мы создали этот метод util во время работы над 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;
ноль

Единственное предостережение, возможно, может повлиять на производительность, но я не квалифицирован, чтобы говорить о том, как различные 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;

Могут быть некоторые крайние случаи с тем, как машинопись выводит типы, и это не скомпилируется, но в целом это должно работать.


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)

Вы также можете пойти другим путем и использовать что-то вроде Lodash ' _.get(). Это сжато, но компилятор не сможет судить о правильности используемых свойств:

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

0

Пока нет (по состоянию на сентябрь 2019 г.), но поскольку «оператор безопасной навигации» сейчас находится на этапе 3 , он реализован в TypeScript.

Следите за обновлениями:

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

Несколько двигателей имеют ранние реализации:

ЗАО: https://bugs.webkit.org/show_bug.cgi?id=200199

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

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

(через https://github.com/tc39/proposal-optional-chaining/issues/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 нам нужен настоящий оператор Элвиса!

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.