Машинописный текст оператора безопасной навигации (?.) Или (!.) И пути к нулевым свойствам


111

В шаблонах Angular2 безопасный оператор ?.работает, но не при component.tsиспользовании TypeScript 2.0. Также не работает безопасный оператор навигации (!.).

Например...

Этот TypeScript

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

компилируется в этот JavaScript

if (a.b.c) { }

Но когда я запускаю его, я получаю следующую ошибку:

Невозможно прочитать свойство "b" неопределенного значения

Есть ли альтернатива следующему?

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

7
Операторы машинописного текста существуют только для компиляции, они не присутствуют в скомпилированном javascript. Отправленная вами ошибка является ошибкой выполнения.
— Ницан Томер

o (o (o (o (test) .level1) .level2) .level3 или o (o (o (o (a) .b) .c) .d stackoverflow.com/a/35169378/3914072, это решение отлично работает для нас во время компиляции, и это с безопасными типами
— Раджаб Шакиров

Ответы:


74

С тех пор, как был выпущен TypeScript 3.7, теперь вы можете использовать дополнительное связывание.

Пример недвижимости:

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

Это эквивалентно:

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

Кроме того, вы можете позвонить:

Дополнительный вызов

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

otherFn будет вызываться только в том случае, если otherFn не будет равно null или undefined

Использование необязательной цепочки в операторе IF

Это:

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

теперь можно заменить этим

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

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


это продолжает выдавать ожидаемую ошибку Expression при компиляции, это работает, если я использую !вместо ?"машинописного текста": "~ 3.5.3"
— Скарамуш

3
@Scaramouche это потому, что ?.в 3.7 был введен дополнительный оператор цепочки ( ), а вы используете 3.5.3. В версиях ранее, чем 3.7, TypeScript предполагает, что ?он используется в сокращении if form ( foo ? bar : baz), и поэтому ожидает остальную часть выражения
— Джордж,

134

!не является нулевым оператором утверждения (выражение после исправления) - он просто сообщает программе проверки типа, что вы уверены, что aэто не так nullили undefined.

операция a!производит значение типа aс nullи undefinedисключено


Дополнительное сцепление , наконец , сделал это машинопись ( 3.7 ) 🎉

Необязательный оператор цепочки ?.позволяет считывать значение свойства, расположенного глубоко в цепочке связанных объектов, без необходимости явно подтверждать, что каждая ссылка в цепочке действительна. В ?.операторе функционирует аналогично .оператору цепной, за исключением того, что вместо того, чтобы вызвать ошибку , если ссылка nullish ( nullили undefined) выражение короткого замыкание с возвращаемым значением undefined. При использовании с вызовами функций возвращается, undefinedесли данная функция не существует.

Синтаксис :

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

Обратите внимание :

Необязательная цепочка недопустима в левой части присваивания.

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

12
Для тех, кто хочет проверить статус этого. TypeScript планирует добавить его, как только ECMAScript добавит поддержку. ECMAScript отслеживает эту проблему здесь .
— Pace

o (o (o (o (test) .level1) .level2) .level3 или o (o (o (o (a) .b) .c) .d stackoverflow.com/a/35169378/3914072, это решение отлично работает для нас во время компиляции, и это с безопасными типами
— Раджаб Шакиров

площадка для новой функции (выйдет 5 ноября): typescriptlang.org/play/?ts=3.7.0-pr-33294-11#code/…
— Нисим Джозеф

1
@ aleksey-l: Возможно, неплохо было бы зачеркнуть материал до "последнего обновления". Это поможет людям, которые не читают сверху вниз (т.е. слишком быстро)! :)
— Айдин

1
Это так здорово - я понятия не имел, что вы можете использовать его для вызовов функций. Искал решение и наткнулся на ваш ответ, так что спасибо!
— Джефф Дэвидс

22

Обновить:

Планируется в рамках выпуска 3.7
https://github.com/microsoft/TypeScript/issues/33352


Вы можете попробовать написать такую ​​кастомную функцию.

Основное преимущество подхода - проверка типов и частичный интеллект.

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

И использование (поддерживает до 5 параметров и может быть расширено):

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

Пример на детской площадке .


1
Это работает хорошо, но у него есть проблема - VSCode не покажет это при использовании «Найти все ссылки» на одной из клавиш.
— Иван Кошелев

Что за проверка типов? В тот момент, когда вы передаете строки как параметры, вы теряете тип. Если это так, вам лучше просто использовать lodash.get
— un33k

16

Другой альтернативой, использующей внешнюю библиотеку, является _.has () от Lodash. .

Например

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

равно

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

РЕДАКТИРОВАТЬ: Как отмечалось в комментариях, вы теряете вывод типа Typescript при использовании этого метода. Например, если предположить, что объекты правильно типизированы, будет получена ошибка компиляции с (a && ab && abz), если z не определен как поле объекта b. Но при использовании _.has (a, 'b.z') такой ошибки не будет.


Хороший совет, хотя другого решения нет, это кажется хорошей альтернативой.
— Димас Крокко

3
Но как это работает с выводом типа TypeScript?
— VitalyB

@VitalyB, вероятно, вы имеете в виду импорт соответствующего интерфейса lodash. (import * as _ from 'lodash';)
— Анкур Арора

1
@AnkurArora, я думаю, что VitalyB имеет в виду, что проверка типов не выполняется с помощью метода Lodash. Например, если предположить, что объекты правильно типизированы, будет получена ошибка компиляции с (a && ab && abz), если z не определен как поле объекта b. Но при использовании _.has (a, 'b.z') такой ошибки не будет. Что касается оператора импорта, да, он определенно нужен.
— TiaanM

1
@TiaanM Я имел в виду именно это. Однако я только что нашел новую библиотеку, которая охватывает проблему типов: stackoverflow.com/a/55462483/126574
— VitalyB 01

3

Новая библиотека ts-optchain обеспечивает эту функциональность, и в отличие от решения lodash, она также обеспечивает безопасность ваших типов. Вот пример того, как она используется (взято из файла 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'))();

2

Основываясь на ответе @Pvl, вы также можете включить безопасность типов в возвращаемое значение, если вы используете переопределения:

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

Пример на детской площадке .

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