ChatGPT解决这个技术问题 Extra ChatGPT

Does Typescript support the ?. operator? (And, what's it called?)

Does Typescript currently (or are there plans to) support the safe navigation operator of ?.

ie:

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

Also, is there a more common name for this operator (it's incedibly hard to google for).

@mattytommo you do have that in c#, its called the null coalescing operator and uses the ?? syntax weblogs.asp.net/scottgu/archive/2007/09/20/…
@BasaratAli Unfortunately not, coalesce is fine for property ?? property2, but if you tried property.company ?? property1.company and property was null, you'd get a NullReferenceException
@mattytommo this does exist now for C#: msdn.microsoft.com/en-us/library/dn986595.aspx
The Microsoft rep that visited us called it the Elvis operator as the question mark looks like Elvis' hair and a microphone he is singing into...
It was added in v3.7 and it's called Optional Chaining. See my answer for code examples.

D
Donut

Yes. As of TypeScript 3.7 (released on November 5, 2019), this feature is supported and is called Optional Chaining:

At its core, optional chaining lets us write code where TypeScript can immediately stop running some expressions if we run into a null or undefined. The star of the show in optional chaining is the new ?. operator for optional property accesses.

Refer to the TypeScript 3.7 release notes for more details.

Prior to version 3.7, this was not supported in TypeScript, although it was requested as early as Issue #16 on the TypeScript repo (dating back to 2014).

As far as what to call this operator, there doesn't appear to be a consensus. In addition to "optional chaining" (which is also what it's called in JavaScript and Swift), there are a couple of other examples:

CoffeeScript refers to it as the existential operator (specifically, the "accessor variant" of the existential operator):

The accessor variant of the existential operator ?. can be used to soak up null references in a chain of properties. Use it instead of the dot accessor . in cases where the base value may be null or undefined.

C# calls this a null-conditional operator.

a null-conditional operator applies a member access, ?., or element access, ?[], operation to its operand only if that operand evaluates to non-null; otherwise, it returns null.

Kotlin refers to it as the safe call operator.

There are probably lots of other examples, too.


"accessor variant of the existential operator". Naturally. So catchy, it's near impossible to forget. :). Thanks for the extremely thorough answer.
@MartyPitt Sure thing! I agree, I'd love to see a) wider adoption of an operator like this (C# please!) and b) a better name (the "safe navigation" operator from your linked blog post has a nice ring to it).
Angular implements this in it's templates: angular.io/guide/…
In some other languages its called the "Elvis" operator
It's announced for TypeScript 3.7.0 (github.com/microsoft/TypeScript/issues/…)
A
A. K-R

It is now possible, see answer of user "Donut".

Old answer: Standard JavaScript behaviour regarding boolean operators has something that may help. The boolean methods do not return true or false when comparing objects, but in case of OR the first value that is equal to true.

Not as nice as a single ?, but it works:

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

You can use as many && as you like:

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

Default values are also possible:

var name = person && person.name || "Unknown user";

&& evaluates as long as the statement is true. If it is true, it returns the last value. If it is false, it returns the first value that evaluated to false. That may be 0, null, false etc. || returns the first value that evaluates to true.
Doesn't work well if the bar is defined but evaluates to false (like boolean false, or zero).
F
Fenton

This is defined in the ECMAScript Optional Chaining specification, so we should probably refer to optional chaining when we discuss this. Likely implementation:

const result = a?.b?.c;

The long and short of this one is that the TypeScript team are waiting for the ECMAScript specification to get tightened up, so their implementation can be non-breaking in the future. If they implemented something now, it would end up needing major changes if ECMAScript redefine their specification.

See Optional Chaining Specification

Where something is never going to be standard JavaScript, the TypeScript team can implement as they see fit, but for future ECMAScript additions, they want to preserve semantics even if they give early access, as they have for so many other features.

Short Cuts

So all of JavaScripts funky operators are available, including the type conversions such as...

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

Manual Solution

But back to the question. I have an obtuse example of how you can do a similar thing in JavaScript (and therefore TypeScript) although I'm definitely not suggesting it is a graceful as the feature you are really after.

(foo||{}).bar;

So if foo is undefined the result is undefined and if foo is defined and has a property named bar that has a value, the result is that value.

I put an example on JSFiddle.

This looks quite sketchy for longer examples.

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

Chain Function

If you are desperate for a shorter version while the specification is still up in the air, I use this method in some cases. It evaluates the expression and returns a default if the chain can't be satisfied or ends up null/undefined (note the != is important here, we don't want to use !== as we want a bit of positive juggling here).

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'));

interesting but in my case (this.loop || {}).nativeElement saying Property 'nativeElement' does not exist on type '{}'. any this.loop typeof angular.io/api/core/ElementRef
@Kuncevic - you need to either... 1) provide a compatible default in place of {}, or 2) use a type assertion to silence the compiler.
Assuming foo is an actual useful object : (foo || {}).bar generally isn't going to compile in typescript because {} won't be of the same type as foo. That's the problem that @VeganHunter's solution aims to avoid.
@Simon_Weaver then (foo || {bar}).bar will let the compiler run smoothly and I think that verbosity is acceptable.
@harps actually this only compiles if bar is defined as a variable, which it wouldn't most likely be
b
basarat

Update: Yes its supported now!

It just got released with TypeScript 3.7 : https://devblogs.microsoft.com/typescript/announcing-typescript-3-7/

It is called optional chaining : https://devblogs.microsoft.com/typescript/announcing-typescript-3-7/#optional-chaining

With it the following:

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

is equivalent to:

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

Old answer

There is an open feature request for this on github where you can voice your opinion / desire : https://github.com/Microsoft/TypeScript/issues/16


J
Jose A

Edit Nov. 13, 2019!

As of November 5, 2019 TypeScript 3.7 has shipped and it now supports ?. the optional chaining operator 🎉🎉🍾🍾🎉!!!

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

For Historical Purposes Only:

Edit: I have updated the answer thanks to fracz comment.

TypeScript 2.0 released !. It's not the same as ?.(Safe Navigator in C#)

See this answer for more details:

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

This will only tell the compiler that the value is not null or undefined. This will not check if the value is null or undefined.

TypeScript Non-null assertion operator

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

Not the same as ? because it asserts that the value is defined. ? is expected to silently fail / evaluate to false. Anyway, good to know.
Now that I think about it... This answer is pretty pointless, because it does not do the "safe navigation" that the C# operator does.
This answered my question, though. I knew about ?. from c# and tried it in typescript. It didn't work, but I saw that !. existed but didn't know what it did. I wondered if it was the same, did a google search, and found my way to this question which informed me that no, they are different.
s
superluminary

The Elvis (?.) Optional Chaining Operator is supported in TypeScript 3.7.

You can use it to check for null values: cats?.miows returns null if cats is null or undefined.

You can also use it for optional method calling: cats.doMiow?.(5) will call doMiow if it exists.

Property access is also possible: cats?.['miows'].

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


Please correct me, but the Elvis operator is at least in Kotlin ?:. Do you have a reference?
Announcement of the TypeScript 3.7 release mentions it: devblogs.microsoft.com/typescript/announcing-typescript-3-7
z
zoran404

It's finally here!

Here are a few examples:

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

But it doesn't work exactly the same as your assumption.

Instead of evaluating

foo?.bar

to this little code snippet we are all used to writing

foo ? foo.bar : null

it actually evaluates to

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

which works for all the falsey values like an empty string, 0 or false.

I just don't have an explanation as to why they don't compile it to foo == null


V
VeganHunter

Operator ?. is not supported in TypeScript version 2.0.

So I use the following function:

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

the usage looks like this:

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

plus, you can set a default value:

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

It works really well with IntelliSense in Visual Studio.


This is exactly what I was looking for! It works in typescript 2.1.6.
or you could call it elvis<T> ;-)
Simon_Weaver, I call it "sad clown" :o(
p
phidias

We created this util method while working on Phonetradr which can give you type-safe access to deep properties with 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(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);


Cool!! Is there any caveats? I have a wrapper class with about 20 getters to write, every one of them has the following type of return - and all fields have to be null checked return this.entry.fields.featuredImage.fields.file.url;
The only caveat might possibly be a performance impact, but I'm not qualified to speak to how the various JITers handle this.
S
Simon_Weaver

I don't generally recommend this approach (watch out for performance concerns), but you can use the spread operator to shallow clone an object, which you can then access the property on.

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

This works because the type of 'firstName' is 'propagated' through.

I'll use this most frequently when I have a find(...) expression that can return null and I need a single property from it:

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

There may be some edge cases with the way typescript infers types and this won't compile, but this should generally work.


D
Damian Green

It's called optional chaining and It's in Typescript 3.7

Optional chaining lets us write code where we can immediately stop running some expressions if we run into a null or undefined


B
Benny Bottema

As answered before, it's currently still being considered but it has been dead in the water for a few years by now.

Building on the existing answers, here's the most concise manual version I can think of:

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'

It simply silently fails on missing property errors. It falls back to the standard syntax for determining default value, which can be omitted completely as well.

Although this works for simple cases, if you need more complex stuff such as calling a function and then access a property on the result, then any other errors are swallowed as well. Bad design.

In the above case, an optimized version of the other answer posted here is the better option:

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'

A more complex example:

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

You can also go the other way and use something like Lodash' _.get(). It is concise, but the compiler won't be able to judge the validity of the properties used:

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

J
Jamon Holmgren

Not yet (as of September, 2019), but since the "safe navigation operator" is now at Stage 3, it's being implemented in TypeScript.

Watch this issue for updates:

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

Several engines have early implementations:

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

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

You can install a plugin to support it now:

npm install --save-dev ts-optchain

In your tsconfig.json:

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

I expect this answer to be out of date in the next 6 months or so, but hopefully it will help someone in the meantime.


J
Josef

I think this is what you're looking for. Similiar example at Powerbite

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

A
Angelos Pikoulas

_.get(obj, 'address.street.name') works great for JavaScript where you have no types. But for TypeScript we need the real Elvis operator!