Bản đánh máy có hỗ trợ không? nhà điều hành? (Và, nó được gọi là gì?)


334

Bản in hiện tại (hoặc có kế hoạch) hỗ trợ toán tử điều hướng an toàn của?.

I E:

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

Ngoài ra, có một tên phổ biến hơn cho nhà điều hành này (thật khó để tìm kiếm google).


3
@mattytommo bạn có cái đó trong c #, nó được gọi là toán tử hợp nhất null và sử dụng ?? cú pháp weblogs.asp.net/scottgu/archive/2007/09/20/20
basarat

2
@BasaratAli Thật không may, hợp tác là tốt property ?? property2, nhưng nếu bạn đã thử property.company ?? property1.companypropertykhông có giá trị, bạn sẽ nhận đượcNullReferenceException
mattytommo

1
@mattytommo Cảm ơn tôi hiểu rồi '?' thực sự ngâm tất cả các tài liệu tham khảo null trong chuỗi. Ngọt.
basarat

9
@mattytommo điều này hiện tồn tại cho C #: msdn.microsoft.com/en-us/l
Library / dn986595.aspx

9
Đại diện của Microsoft đã ghé thăm chúng tôi gọi đó là nhà điều hành Elvis vì dấu hỏi trông giống tóc của Elvis và micro mà anh ấy đang hát vào ...
Zymotik

Câu trả lời:


165

Cập nhật : nó được hỗ trợ kể từ TypeScript 3.7 và được gọi là Chuỗi tùy chọn : https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#optional-chained

Tôi không thể tìm thấy bất kỳ tài liệu tham khảo nào trong đặc tả ngôn ngữ TypeScript .

Theo như cách gọi toán tử này trong CoffeeScript, thì nó được gọi là toán tử hiện sinh (cụ thể là "biến thể truy cập" của toán tử hiện sinh).

Từ tài liệu của CoffeeScript về Người vận hành :

Biến thể truy cập của toán tử hiện sinh ?.có thể được sử dụng để thu thập các tham chiếu null trong một chuỗi các thuộc tính. Sử dụng nó thay cho bộ truy cập dấu chấm .trong trường hợp giá trị cơ sở có thể là null hoặc không xác định .

Vì vậy, biến thể truy cập của toán tử hiện sinh dường như là cách thích hợp để tham chiếu đến toán tử này; và TypeScript hiện không xuất hiện để hỗ trợ nó (mặc dù những người khác đã bày tỏ mong muốn về chức năng này ).


28
"Biến thể truy cập của toán tử hiện sinh". Một cách tự nhiên. Rất hấp dẫn, gần như không thể quên. :). Cảm ơn câu trả lời cực kỳ kỹ lưỡng.
Marty Pitt

1
@MartyPitt Điều chắc chắn! Tôi đồng ý, tôi rất muốn thấy một) việc áp dụng rộng rãi hơn cho một nhà điều hành như thế này (xin vui lòng!) Và b) một tên tốt hơn (nhà điều hành "điều hướng an toàn" từ bài đăng trên blog được liên kết của bạn có một vòng rất hay).
Donut

2
Angular thực hiện điều này trong các mẫu của nó: angular.io/guide/,
Enzoaeneas

5
Trong một số ngôn ngữ khác, nó được gọi là toán tử "Elvis"
k0enf0rNL

4
Nó được công bố cho TypeScript 3.7.0 ( github.com/microsoft/TypeScript/issues/, )
c_froehlich

145

Không đẹp như một?, Nhưng nó hoạt động:

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

Bạn có thể sử dụng bao nhiêu && tùy thích:

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

33
&& đánh giá miễn là tuyên bố là đúng. Nếu nó đúng, nó trả về giá trị cuối cùng. Nếu nó sai, nó trả về giá trị đầu tiên được ước tính thành false. Đó có thể là 0, null, sai, v.v. || trả về giá trị đầu tiên đánh giá là đúng.
A. KR

34
Không hoạt động tốt nếu thanh được xác định nhưng đánh giá thành false (như boolean false hoặc zero).
mt_serg

96

Điều này được định nghĩa trong đặc tả Chuỗi tùy chọn ECMAScript, vì vậy có lẽ chúng ta nên tham khảo chuỗi tùy chọn khi chúng ta thảo luận về điều này. Có khả năng thực hiện:

const result = a?.b?.c;

Cái dài và ngắn của cái này là nhóm TypeScript đang chờ đặc tả ECMAScript được thắt chặt, do đó việc triển khai của chúng có thể không bị phá vỡ trong tương lai. Nếu họ thực hiện một cái gì đó ngay bây giờ, cuối cùng sẽ cần những thay đổi lớn nếu ECMAScript xác định lại đặc điểm kỹ thuật của họ.

Xem thông số kỹ thuật Chaining tùy chọn

Trong trường hợp thứ gì đó sẽ không bao giờ là JavaScript tiêu chuẩn, nhóm TypeScript có thể triển khai khi họ thấy phù hợp, nhưng đối với các bổ sung ECMAScript trong tương lai, họ muốn duy trì ngữ nghĩa ngay cả khi họ cấp quyền truy cập sớm, vì họ có rất nhiều tính năng khác.

Đường cắt ngắn

Vì vậy, tất cả các toán tử vui nhộn của JavaScripts đều có sẵn, bao gồm các chuyển đổi loại như ...

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

Giải pháp thủ công

Nhưng trở lại câu hỏi. Tôi có một ví dụ khó hiểu về cách bạn có thể làm một điều tương tự trong JavaScript (và do đó là TypeScript) mặc dù tôi chắc chắn không gợi ý nó là một tính năng duyên dáng như tính năng bạn thực sự theo đuổi.

(foo||{}).bar;

Vì vậy, nếu fooundefinedkết quả là undefinedvà nếu foođược định nghĩa và có một tài sản có tên barmà có một giá trị, kết quả là giá trị đó.

Tôi đặt một ví dụ trên JSFiddle .

Điều này có vẻ khá sơ sài cho các ví dụ dài hơn.

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

Chức năng chuỗi

Nếu bạn đang mong muốn một phiên bản ngắn hơn trong khi thông số kỹ thuật vẫn còn trong không khí, tôi sử dụng phương pháp này trong một số trường hợp. Nó đánh giá biểu thức và trả về mặc định nếu chuỗi không thể thỏa mãn hoặc kết thúc null / không xác định (lưu ý điều !=quan trọng ở đây, chúng tôi không muốn sử dụng !==vì chúng tôi muốn một chút tung hứng tích cực ở đây).

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

1
thú vị nhưng trong trường hợp của tôi (this.loop || {}).nativeElementnói Property 'nativeElement' does not exist on type '{}'. any this.looptypeof angular.io/api/core/EuityRef
kuncevic.dev

@Kuncevic - bạn cần ... 1) cung cấp mặc định tương thích thay cho {}hoặc 2) sử dụng xác nhận loại để tắt tiếng trình biên dịch.
Fenton

Giả sử foolà một đối tượng hữu ích thực tế: (foo || {}).barthường sẽ không biên dịch trong bản thảo vì {}sẽ không cùng loại với foo. Đó là vấn đề mà giải pháp của @ VeganHunter nhằm tránh.
Simon_Weaver

1
@Simon_Weaver sau đó (foo || {bar}). Thanh sẽ cho phép trình biên dịch chạy trơn tru và tôi nghĩ rằng mức độ chi tiết có thể chấp nhận được.
Harps

@harps thực sự điều này chỉ biên dịch nếu thanh được định nghĩa là một biến, mà rất có thể nó sẽ không xảy ra
Simon_Weaver

81

Cập nhật: Có nó được hỗ trợ ngay bây giờ!

Nó vừa được phát hành với TypeScript 3.7: https://devbloss.microsoft.com/typescript/announcing-typescript-3-7/

Nó được gọi là chuỗi tùy chọn : https://devbloss.microsoft.com/typescript/announcing-typescript-3-7/#optional-chained

Với nó như sau:

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

tương đương với:

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

Câu trả lời cũ

Có một yêu cầu tính năng mở cho điều này trên github nơi bạn có thể nói lên ý kiến ​​/ mong muốn của mình: https://github.com/Microsoft/TypeScript/issues/16


36

Chỉnh sửa ngày 13 tháng 11 năm 2019!

Kể từ ngày 5 tháng 11 năm 2019, TypeScript 3.7 đã xuất xưởng và giờ đâyhỗ trợ?. toán tử xích tùy chọn 🎉🎉🍾🍾🎉 !!!

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


Chỉ dành cho mục đích lịch sử:

Chỉnh sửa: Tôi đã cập nhật câu trả lời nhờ bình luận của fracz.

TypeScript 2.0 được phát hành !.Nó không giống như ?.(Bộ điều hướng an toàn trong C #)

Xem câu trả lời này để biết thêm chi tiết:

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

Điều này sẽ chỉ cho trình biên dịch biết rằng giá trị không phải là null hoặc không xác định. Điều này sẽ không kiểm tra nếu giá trị là null hoặc không xác định.

Toán tử xác nhận TypeScript Non-null

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

4
Không giống như ?bởi vì nó khẳng định rằng giá trị được xác định. ?dự kiến ​​sẽ âm thầm thất bại / đánh giá thành sai. Dù sao, tốt để biết.
fracz

1
Bây giờ tôi nghĩ về nó ... Câu trả lời này khá vô nghĩa, bởi vì nó không thực hiện "điều hướng an toàn" mà nhà điều hành C # làm.
Jose A

5
Điều này đã trả lời câu hỏi của tôi, mặc dù. Tôi biết về? từ c # và đã thử nó trong bản thảo. Nó không hoạt động, nhưng tôi thấy điều đó! tồn tại nhưng không biết nó đã làm gì. Tôi tự hỏi nếu nó giống nhau, đã tìm kiếm trên google và tìm đường đến câu hỏi này cho tôi biết rằng không, chúng khác nhau.
Llewey

11

Toán tử chuỗi tùy chọn Elvis (?.) Được hỗ trợ trong TypeScript 3.7.

Bạn có thể sử dụng nó để kiểm tra giá trị null: cats?.miowstrả về null nếu mèo là null hoặc không xác định.

Bạn cũng có thể sử dụng nó để gọi phương thức tùy chọn: cats.doMiow?.(5)sẽ gọi doMiow nếu nó tồn tại.

Truy cập tài sản cũng có thể : cats?.['miows'].

Tham khảo: https://devbloss.microsoft.com/typescript/announcing-typescript-3-7-beta/


Xin hãy sửa cho tôi, nhưng toán tử Elvis ít nhất là trong Kotlin ?:. Bạn đã có một tài liệu tham khảo?
thiệu


1
Nó sẽ sớm được hỗ trợ trong JS đơn giản - developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
mẹo

1
Thông báo về bản phát hành TypeScript
Gyorgy Balássy

10

Toán tử ?.không được hỗ trợ trong TypeScript phiên bản 2.0 .

Vì vậy, tôi sử dụng chức năng sau đây:

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

việc sử dụng trông như thế này:

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

Ngoài ra, bạn có thể đặt giá trị mặc định:

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

Nó hoạt động thực sự tốt với IntelliSense trong Visual Studio.


1
Điều này thật đúng với gì mà tôi đã tìm kiếm! Nó hoạt động trong bản thảo 2.1.6.
Rajab Shakirov

5
hoặc bạn có thể gọi nó elvis<T>;-)
Simon_Weaver

3
Simon_Weaver, tôi gọi nó là "chú hề buồn": o (
VeganHunter

5

Cuối cùng cũng đến rồi!

Đây là vài ví dụ:

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

Nhưng nó không hoạt động chính xác như giả định của bạn.

Thay vì đánh giá

foo?.bar

với đoạn mã nhỏ này, tất cả chúng ta đều quen viết

foo ? foo.bar : null

nó thực sự đánh giá

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

hoạt động cho tất cả các giá trị falsey như một chuỗi rỗng, 0 hoặc sai.

Tôi chỉ không có một lời giải thích về lý do tại sao họ không biên dịch nó thành foo == null


3

Chúng tôi đã tạo phương thức sử dụng này trong khi làm việc trên Phonetradr , có thể cung cấp cho bạn quyền truy cập an toàn kiểu vào các thuộc tính sâu với Bản in:

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


Mát mẻ!! Có bất kỳ cảnh báo? Tôi có một lớp bao bọc với khoảng 20 getters để viết, mỗi một trong số chúng có kiểu trả về sau - và tất cả các trường phải được kiểm tra nullreturn this.entry.fields.featuredImage.fields.file.url;
Drenai

Sự cảnh báo duy nhất có thể có thể là một tác động hiệu suất, nhưng tôi không đủ điều kiện để nói về cách các JITers khác nhau xử lý việc này.
Ray Suelzer

2

Nói chung, tôi không khuyên bạn nên sử dụng phương pháp này (coi chừng các lo ngại về hiệu suất), nhưng bạn có thể sử dụng toán tử trải rộng để sao chép một đối tượng nông, sau đó bạn có thể truy cập vào thuộc tính.

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

Điều này hoạt động vì loại 'FirstName' được 'truyền bá' thông qua.

Tôi sẽ sử dụng điều này thường xuyên nhất khi tôi có một find(...)biểu thức có thể trả về null và tôi cần một thuộc tính duy nhất từ ​​nó:

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

Có thể có một số trường hợp cạnh với cách sắp xếp kiểu chữ và kiểu này sẽ không được biên dịch, nhưng điều này thường sẽ hoạt động.


2

Nó được gọi là chuỗi tùy chọn và nó trong Bản in 3.7

Chuỗi tùy chọn cho phép chúng tôi viết mã nơi chúng tôi có thể ngay lập tức ngừng chạy một số biểu thức nếu chúng tôi chạy vào null hoặc không xác định


0

Như đã trả lời trước đó, hiện tại nó vẫn đang được xem xét nhưng nó đã chết trong nước vài năm rồi.

Dựa trên các câu trả lời hiện có, đây là phiên bản thủ công ngắn gọn nhất mà tôi có thể nghĩ ra:

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'

Nó chỉ đơn giản là thất bại trong lỗi thiếu tài sản. Nó rơi trở lại cú pháp tiêu chuẩn để xác định giá trị mặc định, cũng có thể được bỏ qua hoàn toàn.


Mặc dù điều này hoạt động cho các trường hợp đơn giản, nhưng nếu bạn cần các công cụ phức tạp hơn như gọi hàm và sau đó truy cập vào một thuộc tính trên kết quả, thì bất kỳ lỗi nào khác cũng bị nuốt. Thiết kế xấu.

Trong trường hợp trên, một phiên bản tối ưu hóa của câu trả lời khác được đăng ở đây là tùy chọn tốt hơn:

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'

Một ví dụ phức tạp hơn:

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

Bạn cũng có thể đi theo con đường khác và sử dụng một cái gì đó như Lodash ' _.get(). Nó ngắn gọn, nhưng trình biên dịch sẽ không thể đánh giá tính hợp lệ của các thuộc tính được sử dụng:

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

0

Chưa (kể từ tháng 9 năm 2019), nhưng vì "nhà điều hành điều hướng an toàn" hiện đang ở Giai đoạn 3 , nên nó được triển khai trong TypeScript.

Xem vấn đề này để cập nhật:

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

Một số động cơ có triển khai sớm:

Công ty cổ phần: https://bugs.webkit.org/show_orms.cgi?id=200199

V8: https://bugs.chromium.org/p/v8/issues/detail?id=9553

SM: https://ormszilla.mozilla.org/show_orms.cgi?id=1566143

(thông qua https://github.com/tc39/proposed-optional-chained/issues/115#su-475422578 )

Bạn có thể cài đặt một plugin để hỗ trợ ngay bây giờ:

npm install --save-dev ts-optchain

Trong tsconfig.json của bạn:

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

Tôi hy vọng câu trả lời này sẽ hết hạn trong 6 tháng tới hoặc lâu hơn, nhưng hy vọng nó sẽ giúp được ai đó trong lúc này.


-1

_.get(obj, 'address.street.name')hoạt động tuyệt vời cho JavaScript khi bạn không có loại. Nhưng đối với TypeScript, chúng ta cần toán tử Elvis thực sự!

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.