ChatGPT解决这个技术问题 Extra ChatGPT

Safe navigation operator (?.) or (!.) and null property paths

In Angular 2 templates safe operator ?. works, but not in component.ts using TypeScript 2.0. Also, safe navigation operator (!.) doesn't work.

For example:

This TypeScript

if (a!.b!.c) { }

compiles to this JavaScript

if (a.b.c) { }

But when I run it, I get the follow error:

Cannot read property 'b' of undefined

Is there any alternative to the following?

if (a && a.b && a.b.c) { }
The typescript operators only exist for compilation, they are not present in the compiled javascript. The error you posted is a runtime error.
o(o(o(o(test).level1).level2).level3 or o(o(o(o(a).b).c).d stackoverflow.com/a/35169378/3914072 this solution works great for us at compilation time and it's with safe types
this is such a difficult thing to Google. Thank you!

A
Aleksey L.

! is non-null assertion operator (post-fix expression) - it just saying to type checker that you're sure that a is not null or undefined.

the operation a! produces a value of the type of a with null and undefined excluded

Optional chaining finally made it to typescript (3.7) 🎉

The optional chaining operator ?. permits reading the value of a property located deep within a chain of connected objects without having to expressly validate that each reference in the chain is valid. The ?. operator functions similarly to the . chaining operator, except that instead of causing an error if a reference is nullish (null or undefined), the expression short-circuits with a return value of undefined. When used with function calls, it returns undefined if the given function does not exist.

Syntax:

obj?.prop // Accessing object's property
obj?.[expr] // Optional chaining with expressions
arr?.[index] // Array item access with optional chaining
func?.(args) // Optional chaining with function calls

Pay attention:

Optional chaining is not valid on the left-hand side of an assignment

const object = {};
object?.property = 1; // Uncaught SyntaxError: Invalid left-hand side in assignment

For those interested in checking in on the status of this. TypeScript is planning on adding it once ECMAScript adds support. ECMAScript is tracking this issue here.
o(o(o(o(test).level1).level2).level3 or o(o(o(o(a).b).c).d stackoverflow.com/a/35169378/3914072 this solution works great for us at compilation time and it's with safe types
playground of the new feature (will release on November 5): typescriptlang.org/play/?ts=3.7.0-pr-33294-11#code/…
@aleksey-l: It might not be a bad idea to strikethrough the stuffs prior to the "last update". It will help people who don't read top to bottom (i.e. too fast)! :)
This is so cool - I had no idea that you could use it for function calls. Was looking for a solution and came across your answer, so thank you!
A
Audwin Oyong

Since TypeScript 3.7 was released you can use optional chaining now.

Property example:

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

This is equvalent to:

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

Moreover you can call:

Optional Call

function(otherFn: (par: string) => void) {
   otherFn?.("some value");
}

otherFn will be called only if otherFn won't be equal to null or undefined

Usage optional chaining in IF statement

This:

if (someObj && someObj.someProperty) {
  // ...
}

can be replaced now with this

if (someObj?.someProperty) {
  // ...
}

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


this keeps throwing Expression expected error when compiling, it works if I use ! instead of ? in "typescript": "~3.5.3"
@Scaramouche this is because the optional chaining operator (?.) was introduced in 3.7 and you're running 3.5.3. In versions earlier than 3.7, TypeScript assumes that ? is being used in the shorthand if form (foo ? bar : baz) and so expects the rest of the expression
N
NaN

Update:

Planned in the scope of 3.7 release
https://github.com/microsoft/TypeScript/issues/33352

You can try to write a custom function like that.

The main advantage of the approach is a type-checking and partial intellisense.

export function nullSafe<T, 
    K0 extends keyof T, 
    K1 extends keyof T[K0],
    K2 extends keyof T[K0][K1],
    K3 extends keyof T[K0][K1][K2],
    K4 extends keyof T[K0][K1][K2][K3],
    K5 extends keyof T[K0][K1][K2][K3][K4]>
    (obj: T, k0: K0, k1?: K1, k2?: K2, k3?: K3, k4?: K4, k5?: K5) {
    let result: any = obj;

    const keysCount = arguments.length - 1;
    for (var i = 1; i <= keysCount; i++) {
        if (result === null || result === undefined) return result;
        result = result[arguments[i]];
    }

    return result;
}

And usage (supports up to 5 parameters and can be extended):

nullSafe(a, 'b', 'c');

Example on playground.


This works nice, but It has a problem - VSCode will not show this when using "Find All References" on one of the keys.
What kind of type checking ? The moment you pass strings in as params, you have lost the type. If so, you are better off to just use lodash.get
T
TiaanM

Another alternative that uses an external library is _.has() from Lodash.

E.g.

_.has(a, 'b.c')

is equal to

(a && a.b && a.b.c)

EDIT: As noted in the comments, you lose out on Typescript's type inference when using this method. E.g. Assuming that one's objects are properly typed, one would get a compilation error with (a && a.b && a.b.z) if z is not defined as a field of object b. But using _.has(a, 'b.z'), one would not get that error.


Nice tip, while no other solution is available, this seems to be a good alternative.
But how does it work with TypeScript's type inference?
@VitalyB probably u mean importing corresponding interface of lodash. (import * as _ from 'lodash';)
@AnkurArora, I think what VitalyB means is that no type checking is done using the Lodash method. E.g. Assuming that one's objects are properly typed, one would get a compilation error with (a && a.b && a.b.z) if z is not defined as a field of object b. But using _.has(a, 'b.z'), one would not get that error. As for the import statement, yes, it is definitely needed.
@TiaanM That's exactly what I meant. I just found a new library, however, that covers the types issue: stackoverflow.com/a/55462483/126574
V
VitalyB

A new library called ts-optchain provides this functionality, and unlike lodash' solution, it also keeps your types safe, here is a sample of how it is used (taken from the readme):

import { oc } from 'ts-optchain';

interface I {
  a?: string;
  b?: {
    d?: string;
  };
  c?: Array<{
    u?: {
      v?: number;
    };
  }>;
  e?: {
    f?: string;
    g?: () => string;
  };
}

const x: I = {
  a: 'hello',
  b: {
    d: 'world',
  },
  c: [{ u: { v: -100 } }, { u: { v: 200 } }, {}, { u: { v: -300 } }],
};

// Here are a few examples of deep object traversal using (a) optional chaining vs
// (b) logic expressions. Each of the following pairs are equivalent in
// result. Note how the benefits of optional chaining accrue with
// the depth and complexity of the traversal.

oc(x).a(); // 'hello'
x.a;

oc(x).b.d(); // 'world'
x.b && x.b.d;

oc(x).c[0].u.v(); // -100
x.c && x.c[0] && x.c[0].u && x.c[0].u.v;

oc(x).c[100].u.v(); // undefined
x.c && x.c[100] && x.c[100].u && x.c[100].u.v;

oc(x).c[100].u.v(1234); // 1234
(x.c && x.c[100] && x.c[100].u && x.c[100].u.v) || 1234;

oc(x).e.f(); // undefined
x.e && x.e.f;

oc(x).e.f('optional default value'); // 'optional default value'
(x.e && x.e.f) || 'optional default value';

// NOTE: working with function value types can be risky. Additional run-time
// checks to verify that object types are functions before invocation are advised!
oc(x).e.g(() => 'Yo Yo')(); // 'Yo Yo'
((x.e && x.e.g) || (() => 'Yo Yo'))();

A
Alec

Building on @Pvl's answer, you can include type safety on your returned value as well if you use overrides:

function dig<
  T,
  K1 extends keyof T
  >(obj: T, key1: K1): T[K1];

function dig<
  T,
  K1 extends keyof T,
  K2 extends keyof T[K1]
  >(obj: T, key1: K1, key2: K2): T[K1][K2];

function dig<
  T,
  K1 extends keyof T,
  K2 extends keyof T[K1],
  K3 extends keyof T[K1][K2]
  >(obj: T, key1: K1, key2: K2, key3: K3): T[K1][K2][K3];

function dig<
  T,
  K1 extends keyof T,
  K2 extends keyof T[K1],
  K3 extends keyof T[K1][K2],
  K4 extends keyof T[K1][K2][K3]
  >(obj: T, key1: K1, key2: K2, key3: K3, key4: K4): T[K1][K2][K3][K4];

function dig<
  T,
  K1 extends keyof T,
  K2 extends keyof T[K1],
  K3 extends keyof T[K1][K2],
  K4 extends keyof T[K1][K2][K3],
  K5 extends keyof T[K1][K2][K3][K4]
  >(obj: T, key1: K1, key2: K2, key3: K3, key4: K4, key5: K5): T[K1][K2][K3][K4][K5];

function dig<
  T,
  K1 extends keyof T,
  K2 extends keyof T[K1],
  K3 extends keyof T[K1][K2],
  K4 extends keyof T[K1][K2][K3],
  K5 extends keyof T[K1][K2][K3][K4]
  >(obj: T, key1: K1, key2?: K2, key3?: K3, key4?: K4, key5?: K5):
  T[K1] |
  T[K1][K2] |
  T[K1][K2][K3] |
  T[K1][K2][K3][K4] |
  T[K1][K2][K3][K4][K5] {
    let value: any = obj && obj[key1];

    if (key2) {
      value = value && value[key2];
    }

    if (key3) {
      value = value && value[key3];
    }

    if (key4) {
      value = value && value[key4];
    }

    if (key5) {
      value = value && value[key5];
    }

    return value;
}

Example on playground.


N
Nilesh Pal

Versions above typescript 3.7 supports safe navigation operator for typescript < 3.7 I made this function which can be useful.

export function isAccessible(data, keys, start=0) {
  if (start == 0 && (data == null || data == undefined)) {
    console.warn("data",data);
    return false;
  } else {
    if (data[keys[start]] == null || data[keys[start]] == undefined) {
      console.warn("Object valid till", keys.slice(0,start),keys[start],"undefined");
      return false;
    } else {
      if (start + 1 >= keys.length) {
        return data[keys[start]];
      }
      return this.isAccessible(data[keys[start]], keys, start + 1);
    }
  }
}

function call in code

Suppose we have a Object obj keys of which can vary and we want to check if obj.key1.key2 is accessible or not then function call will be as follows:

isAccessible(Object,["key1","key2"])