Typescript是否支持?。操作员?(又叫什么?)


336

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/...
basarat

2
@BasaratAli不幸的是,结盟对没问题,property ?? property2但是如果您尝试property.company ?? property1.company并且property为空,那么您会得到NullReferenceException
mattytommo

1
@mattytommo谢谢,我现在明白了吗?。实际上将链中的所有空引用都吸收。甜。
basarat

9
@mattytommo对于C#现在确实存在:msdn.microsoft.com/zh-cn/library/dn986595.aspx
Highmastdon 2015年

9
拜访过我们的Microsoft代表称其为Elvis操作员,因为问号看起来像是Elvis的头发和他正在唱歌的麦克风...
Zymotik

Answers:


167

更新:从TypeScript 3.7开始受支持,称为“ 可选链接”https : //www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#optional-chaining

我在TypeScript语言规范中找不到任何对此的引用。

至于在CoffeeScript中调用该运算符的方式,它称为存在运算符(特别是,存在运算符的“访问器变量”)。

CoffeeScript的Operators文档中

存在性运算符的accessor变体?.可以用于吸收属性链中的空引用。.如果基值可能为null未定义,请使用它代替点访问器。

因此,存在性运算符accessor变体似乎是引用该运算符的正确方法。并且TypeScript当前似乎不支持它(尽管其他人已经表达了对此功能的渴望)。


28
“存在运算符的访问器变体”。自然。如此吸引人,几乎无法忘记。:)。感谢您的彻底答复。
马蒂·皮特

1
@MartyPitt当然可以!我同意,我很乐意看到a)像这样的运算符得到更广泛的采用(请C#!),以及b)更好的名称(链接的博客文章中的“安全导航”运算符对此具有很好的响度)。
Donut 2013年

2
Angular在其模板中实现了这一点:angular.io/guide/…–
Enzoaeneas

5
在某些其他语言中,其称为“猫王”运算符
k0enf0rNL

4
它宣布了打字稿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
只要陈述为真,&&就会求值。如果为true,则返回最后一个值。如果为false,则返回评估为false的第一个值。可以是0,null,false等。返回评估为true的第一个值。
A. KR

34
如果定义了条形,但计算结果为false(如布尔值false或零),则效果不佳。
mt_serg '17

96

这是在ECMAScript可选链接规范中定义的,因此我们在讨论时可能应该参考可选链接。可能的实现:

const result = a?.b?.c;

这样做的长处和短处是TypeScript团队正在等待ECMAScript规范收紧,因此它们的实现将来可能会不断发展。如果他们现在实施了某些措施,那么如果ECMAScript重新定义其规范,最终将需要进行重大更改。

请参阅可选的链接规范

在某些东西永远不会成为标准JavaScript的地方,TypeScript团队可以按照自己的意愿实施,但是对于将来的ECMAScript增补,他们希望保留语义,即使它们提供了早期访问权限,就像许多其他功能一样。

捷径

因此,所有JavaScript时髦的运算符都可用,包括类型转换,例如...

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

手动解决方案

但是回到问题。我有一个令人费解的示例,说明了如何在JavaScript(以及TypeScript)中执行类似的操作,尽管我绝对不建议它是您真正追求的功能。

(foo||{}).bar;

所以,如果fooundefined的结果是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)使用类型断言来使编译器静音。
Fenton

假设foo是一个实际有用的对象:(foo || {}).bar通常不会在typescript中进行编译,因为{}不会与foo。这就是@VeganHunter解决方案旨在避免的问题。
Simon_Weaver

1
@Simon_Weaver然后(foo || {bar})。bar将使编译器平稳运行,我认为冗长是可以接受的。
竖琴

@harps实际上只有在将bar定义为变量的情况下才编译,而这不太可能
Simon_Weaver19年

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/issues/16


36

编辑2019年11月13日!

截至2019年11月5日,TypeScript 3.7已交付,现在支持 ?.可选的链接运算符🎉🎉🍾🍾🎉!!!

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


仅出于历史目的:

编辑:感谢fracz评论,我已经更新了答案。

TypeScript 2.0发布!.?.(C#中的Safe Navigator)不同

查看此答案以获取更多详细信息:

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

这只会告诉编译器该值不是null或未定义。这不会检查该值是否为null或未定义。

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#操作员那样进行“安全导航”。
何塞A

5
但这回答了我的问题。我知道吗?从C#并在打字稿中尝试过。它没有用,但是我看到了!存在,但不知道它做了什么。我想知道是否相同,做了一个google搜索,然后找到了解决这个问题的方法,这告诉我不,它们是不同的。
Llewey

11

TypeScript 3.7支持Elvis(?。)可选链接运算符。

您可以使用它来检查null值:cats?.miows如果cats为null或未定义,则返回null。

您也可以将其用于可选的方法调用:cats.doMiow?.(5)如果存在,将调用doMiow。

也可以访问媒体资源:cats?.['miows']

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


请纠正我,但Elvis运算符至少在Kotlin中?:。你有参考吗?
休息




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")

它可以与Visual Studio中的IntelliSense一起很好地工作。


1
这正是我想要的!它适用于打字稿2.1.6。
Rajab Shakirov

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

我们在处理Phonetradr时创建了这个util方法,该方法可以使您使用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个getter编写,每个包装都具有以下类型的返回值-所有字段都必须进行空检查return this.entry.fields.featuredImage.fields.file.url;
Drenai

唯一的警告可能是对性能的影响,但是我没有资格谈论各种JITer如何处理此问题。
Ray Suelzer '18

2

我通常不推荐这种方法(注意性能问题),但是您可以使用spread运算符来浅克隆对象,然后可以访问该对象。

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

之所以有效,是因为“名字”的类型是“传播”的。

当我有一个find(...)可以返回null 的表达式并且需要一个单独的属性时,我将经常使用它:

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

打字稿推断类型的方式可能存在一些极端情况,因此无法编译,但这通常应该可以工作。



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年9月),但是由于“安全导航操作员” 现在处于第3阶段,因此正在TypeScript中实现。

观看此问题以获取更新:

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

有几种引擎有早期实现:

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

(通过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个月左右会过时,但希望在此期间对您有所帮助。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.