Наберіть сценарій оператора безпечної навігації (?.) Або (!.) Та обнулі шляхи властивості


109

У шаблонах 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. Опублікована вами помилка - це помилка виконання.
— Нітзан Томер,

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

Відповіді:


69

З моменту випуску 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) {
    // ...
}

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


це продовжує викидати очікувану помилку виразу при компілюванні, вона працює, якщо я використовую !замість ?"typecript": "~ 3.5.3"
— Scaramouche

3
@Scaramouche це тому, що необов'язковий оператор ланцюга ( ?.) був введений в 3.7, а ви працюєте з 3.5.3. У версіях, що раніше, ніж 3.7, TypeScript припускає, ?що використовується у скороченні, якщо форма ( 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 відстежує це питання тут .
— Темп

про (про (про (про (тест) .level1) .level2) .level3 або про (про (про (про (а) .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/isissue/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, ймовірно, має на увазі імпорт відповідного інтерфейсу lodash. (імпорт * як _ з 'lodash';)
— Ankur Arora

1
@AnkurArora, я думаю, що VitalyB означає, що перевірка типу не проводиться методом Лодаша. Наприклад, якщо припустити, що об’єкти належним чином набрані, ви отримаєте помилку компіляції з (a&& ab && abz), якщо z не визначено як поле об'єкта b. Але, використовуючи _.has (a, 'b.z'), ця помилка не з’явиться. Щодо заяви про імпорт, так, вона, безумовно, потрібна.
— TiaanM

1
@TiaanM Саме так я і мав на увазі. Я тільки що знайшов нову бібліотеку, однак, що охоплює типи видають: stackoverflow.com/a/55462483/126574
— VitalyB

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.