Các kiểu chỉ định toán tử điều hướng an toàn (?.) Hoặc (!.) Và đường dẫn thuộc tính null


109

Trong Angular2, toán tử an toàn ?.hoạt động, nhưng không hoạt động khi component.tssử dụng TypeScript 2.0. Ngoài ra, toán tử điều hướng an toàn (!.) Không hoạt động.

Ví dụ...

TypeScript này

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

biên dịch sang JavaScript này

if (a.b.c) { }

Nhưng khi tôi chạy nó, tôi gặp lỗi sau:

Không thể đọc thuộc tính 'b' của không xác định

Có cách thay thế nào sau đây không?

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

7
Các toán tử biên dịch chỉ tồn tại để biên dịch, chúng không có trong javascript đã biên dịch. Lỗi bạn đã đăng là lỗi thời gian chạy.
Nitzan Tomer

o (o (o (o (test) .level1) .level2) .level3 hoặc o (o (o (o (a) .b) .c) .d stackoverflow.com/a/35169378/3914072) giải pháp này hoạt động tốt cho chúng tôi tại thời gian biên dịch và đó là với các loại an toàn
Rajab Shakirov

Câu trả lời:


68

Kể từ khi TypeScript 3.7 được phát hành, bạn có thể sử dụng chuỗi tùy chọn ngay bây giờ.

Ví dụ về tài sản:

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

Điều này tương đương với:

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

Hơn nữa bạn có thể gọi:

Cuộc gọi tùy chọn

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

otherFn sẽ chỉ được gọi nếu otherFn không bằng null hoặc không xác định

Sử dụng chuỗi tùy chọn trong câu lệnh IF

Điều này:

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

có thể được thay thế bây giờ bằng cái này

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

Tham khảo https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html


điều này tiếp tục gây ra lỗi Biểu thức mong đợi khi biên dịch, nó hoạt động nếu tôi sử dụng !thay vì ?trong "typecript": "~ 3.5.3"
Scaramouche

3
@Scaramouche điều này là do toán tử chuỗi tùy chọn ( ?.) đã được giới thiệu trong 3.7 và bạn đang chạy 3.5.3. Trong các phiên bản trước 3.7, TypeScript giả định rằng nó ?đang được sử dụng ở dạng viết tắt if ( foo ? bar : baz) và do đó, mong đợi phần còn lại của biểu thức
George

134

!toán tử khẳng định không null (biểu thức sau sửa chữa) - nó chỉ cho biết nhập trình kiểm tra mà bạn chắc chắn rằng akhông phải nullhoặc undefined.

hoạt động a!tạo ra một giá trị thuộc loại anullundefinedloại trừ


Chuỗi tùy chọn cuối cùng đã được đưa vào bảng chữ ( 3.7 ) 🎉

Toán tử chuỗi tùy chọn ?.cho phép đọc giá trị của một thuộc tính nằm sâu trong chuỗi các đối tượng được kết nối mà không cần phải xác nhận rõ ràng rằng mỗi tham chiếu trong chuỗi là hợp lệ. Các ?.chức năng điều hành tương tự như các .nhà điều hành loạt, ngoại trừ việc thay vì gây ra một lỗi nếu một tài liệu tham khảo là nullish ( nullhoặc undefined), khái niệm ngắn mạch với một giá trị trả về của undefined. Khi được sử dụng với các lệnh gọi hàm, nó sẽ trả về undefinednếu hàm đã cho không tồn tại.

Cú pháp :

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

Chú ý :

Chuỗi tùy chọn không hợp lệ ở phía bên trái của bài tập

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

12
Đối với những người quan tâm đến việc kiểm tra trạng thái của điều này. TypeScript đang có kế hoạch thêm nó khi ECMAScript hỗ trợ thêm. ECMAScript đang theo dõi vấn đề này ở đây .
Nhịp độ

o (o (o (o (test) .level1) .level2) .level3 hoặc o (o (o (o (a) .b) .c) .d stackoverflow.com/a/35169378/3914072) giải pháp này hoạt động tốt cho chúng tôi tại thời gian biên dịch và đó là với các loại an toàn
Rajab Shakirov

sân chơi của tính năng mới (sẽ phát hành vào ngày 5 tháng 11): stylescriptlang.org/play/?ts=3.7.0-pr-33294-11#code/…
Nisim Joseph

1
@ aleksey-l: Có thể không phải là một ý tưởng tồi nếu gạch ngang những thứ trước "bản cập nhật cuối cùng". Nó sẽ giúp những người không đọc từ trên xuống dưới (tức là quá nhanh)! :)
Aidin

1
Điều này thật tuyệt - Tôi không biết rằng bạn có thể sử dụng nó cho các cuộc gọi hàm. Tôi đang tìm kiếm một giải pháp và đã xem câu trả lời của bạn, vì vậy cảm ơn bạn!
Geoff Davids

22

Cập nhật:

Được lập kế hoạch trong phạm vi bản phát hành 3.7
https://github.com/microsoft/TypeScript/issues/33352


Bạn có thể thử viết một hàm tùy chỉnh như vậy.

Ưu điểm chính của phương pháp này là kiểm tra kiểu và một phần 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;
}

Và cách sử dụng (hỗ trợ tối đa 5 tham số và có thể được mở rộng):

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

Ví dụ trên sân chơi .


1
Điều này hoạt động tốt, nhưng nó có một vấn đề - VSCode sẽ không hiển thị điều này khi sử dụng "Tìm tất cả tài liệu tham khảo" trên một trong các phím.
Ivan Koshelev

Kiểm tra kiểu gì? Thời điểm bạn chuyển chuỗi vào dưới dạng tham số, bạn đã mất kiểu. Nếu vậy, bạn tốt hơn hết chỉ lodash.get sử dụng
un33k

16

Một giải pháp thay thế khác sử dụng thư viện bên ngoài là _.has () từ Lodash .

Ví dụ

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

bằng

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

CHỈNH SỬA: Như đã lưu ý trong các nhận xét, bạn sẽ mất khả năng suy luận kiểu của Typecript khi sử dụng phương pháp này. Ví dụ: Giả sử rằng các đối tượng của một người được nhập đúng, người ta sẽ gặp lỗi biên dịch với (a && ab && abz) nếu z không được định nghĩa là một trường của đối tượng b. Nhưng sử dụng _.has (a, 'b.z'), người ta sẽ không gặp lỗi đó.


Mẹo hay, trong khi không có giải pháp nào khác, đây có vẻ là một giải pháp thay thế tốt.
Dimas Crocco

3
Nhưng nó hoạt động như thế nào với kiểu suy luận của TypeScript?
VitalyB

@VitalyB có lẽ bạn đang nhập giao diện tương ứng của lodash. (nhập * dưới dạng _ từ 'lodash';)
Ankur Arora

1
@AnkurArora, tôi nghĩ ý của VitalyB là không có kiểm tra kiểu nào được thực hiện bằng phương pháp Lodash. Ví dụ: Giả sử rằng các đối tượng của một người được nhập đúng, người ta sẽ gặp lỗi biên dịch với (a && ab && abz) nếu z không được định nghĩa là một trường của đối tượng b. Nhưng sử dụng _.has (a, 'b.z'), người ta sẽ không gặp lỗi đó. Đối với câu lệnh nhập khẩu, có, nó chắc chắn là cần thiết.
TiaanM

1
@TiaanM Đó chính xác là ý của tôi. Tuy nhiên, tôi vừa tìm thấy một thư viện mới bao gồm các vấn đề về loại: stackoverflow.com/a/55462483/126574
VitalyB

3

Một thư viện mới có tên ts-optchain cung cấp chức năng này và không giống như giải pháp của lodash, nó cũng giữ an toàn cho các loại của bạn, đây là ví dụ về cách nó được sử dụng (lấy từ 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

Dựa trên câu trả lời của @ Pvl, bạn cũng có thể bao gồm an toàn loại trên giá trị trả về của mình nếu bạn sử dụng ghi đè:

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

Ví dụ trên sân chơi .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.