Apakah TypeScript mendukung? operator? (Dan, apa namanya?)

337

Apakah Filescript saat ini (atau ada rencana untuk) mendukung operator navigasi yang aman dari?.

yaitu:

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

Juga, apakah ada nama yang lebih umum untuk operator ini (sangat sulit untuk google).

Marty Pitt
sumber
3
@mattytommo Anda memilikinya di c #, yang disebut operator penggabungan nol dan menggunakan ?? syntax weblogs.asp.net/scottgu/archive/2007/09/20/…
basarat
2
@BasaratAli Sayangnya tidak, menyatu baik-baik saja untuk property ?? property2, tetapi jika Anda mencoba property.company ?? property1.companydan propertynull, Anda akan mendapatkanNullReferenceException
mattytommo
1
@mattytommo Terima kasih, saya mengerti sekarang '?' sebenarnya membasahi semua referensi nol dalam rantai. Manis.
basarat
9
@mattytommo ini ada sekarang untuk C #: msdn.microsoft.com/en-us/library/dn986595.aspx
Highmastdon
9
Perwakilan Microsoft yang mengunjungi kami menyebutnya operator Elvis sebagai tanda tanya yang terlihat seperti rambut Elvis dan mikrofon yang dinyanyikannya ...
Zymotik

Jawaban:

168

Pembaruan : didukung pada TypeScript 3.7 dan disebut Chaining opsional : https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#optional-chaining

Saya tidak dapat menemukan referensi apa pun di spesifikasi bahasa TypeScript .

Sejauh apa yang memanggil operator ini di CoffeeScript, itu disebut operator eksistensial (khusus, "varian accessor" dari operator eksistensial).

Dari dokumentasi CoffeeScript tentang Operator :

Varian accessor dari operator eksistensial ?.dapat digunakan untuk menyerap referensi nol dalam rantai properti. Gunakan itu sebagai ganti pengakses titik .dalam kasus-kasus di mana nilai dasar mungkin nol atau tidak terdefinisi .

Jadi, varian accessor dari operator eksistensial tampaknya menjadi cara yang tepat untuk merujuk ke operator ini; dan TypeScript saat ini tidak muncul untuk mendukungnya (walaupun orang lain telah menyatakan keinginan untuk fungsi ini ).

Donat
sumber
28
+ msgstr "varian accessor dari operator eksistensial". Tentu saja. Sangat menarik, hampir mustahil untuk dilupakan. :) Terima kasih atas jawaban yang sangat teliti.
Marty Pitt
1
@MartyPitt Tentu saja! Saya setuju, saya ingin melihat a) adopsi yang lebih luas dari operator seperti ini (C # tolong!) Dan b) nama yang lebih baik (operator "navigasi aman" dari posting blog Anda yang terhubung memiliki cincin yang bagus untuk itu).
Donat
2
Angular mengimplementasikan ini dalam templatnya
Enzoaeneas
5
Dalam beberapa bahasa lain disebut operator "Elvis"
k0enf0rNL
4
Diumumkan untuk TypeScript 3.7.0 ( github.com/microsoft/TypeScript/issues/… )
c_froehlich
146

Tidak sebagus satu? Tapi itu berfungsi:

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

Anda dapat menggunakan sebanyak && sebanyak yang Anda suka:

var thing = foo && foo.bar && foo.bar.check && foo.bar.check.x || null;
A. KR
sumber
33
&& mengevaluasi selama pernyataan itu benar. Jika itu benar, ia mengembalikan nilai terakhir. Jika itu salah, ia mengembalikan nilai pertama yang dievaluasi menjadi salah. Itu mungkin 0, null, false dll || mengembalikan nilai pertama yang mengevaluasi ke true.
A. KR
34
Tidak berfungsi dengan baik jika bilah didefinisikan tetapi dievaluasi menjadi false (seperti boolean false, atau nol).
mt_serg
96

Ini didefinisikan dalam spesifikasi ECMAScript Optional Chaining, jadi kita mungkin harus merujuk ke chaining opsional ketika kita membahas ini. Kemungkinan implementasi:

const result = a?.b?.c;

Yang panjang dan pendek dari yang satu ini adalah bahwa tim TypeScript sedang menunggu spesifikasi ECMAScript untuk diperketat, sehingga implementasinya dapat menjadi non-breaking di masa depan. Jika mereka mengimplementasikan sesuatu sekarang, itu akan berakhir membutuhkan perubahan besar jika ECMAScript mendefinisikan ulang spesifikasi mereka.

Lihat Spesifikasi Rantai Opsional

Di mana sesuatu tidak akan pernah menjadi JavaScript standar, tim TypeScript dapat menerapkan sesuai keinginan mereka, tetapi untuk penambahan ECMAScript di masa depan, mereka ingin mempertahankan semantik bahkan jika mereka memberikan akses awal, seperti yang mereka miliki untuk banyak fitur lainnya.

Potongan pendek

Jadi semua operator funky JavaScripts tersedia, termasuk jenis konversi seperti ...

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

Solusi Manual

Tapi kembali ke pertanyaan. Saya punya contoh tumpul tentang bagaimana Anda dapat melakukan hal serupa di JavaScript (dan karena itu TypeScript) meskipun saya jelas tidak menyarankan itu adalah anggun sebagai fitur yang benar-benar Anda cari.

(foo||{}).bar;

Jadi jika fooini undefinedhasilnya adalah undefineddan jika foodidefinisikan dan memiliki properti bernama baryang memiliki nilai, hasilnya adalah nilai itu.

Saya memberi contoh di JSFiddle .

Ini terlihat cukup samar untuk contoh yang lebih panjang.

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

Fungsi Rantai

Jika Anda putus asa untuk versi yang lebih pendek sementara spesifikasinya masih tinggi, saya menggunakan metode ini dalam beberapa kasus. Ini mengevaluasi ekspresi dan mengembalikan default jika rantai tidak dapat dipenuhi atau berakhir nol / tidak terdefinisi (perhatikan !=ini penting di sini, kami tidak ingin menggunakan !==karena kami ingin sedikit juggling positif di sini).

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'));
Fenton
sumber
1
menarik tetapi dalam kasus saya (this.loop || {}).nativeElementmengatakan Property 'nativeElement' does not exist on type '{}'. any this.looptypeof angular.io/api/core/ElementRef
kuncevic.dev
@Kuncevic - Anda harus ... 1) memberikan default yang kompatibel sebagai pengganti {}, atau 2) menggunakan pernyataan tipe untuk membungkam kompiler.
Fenton
Mengasumsikan fooadalah objek berguna yang sebenarnya: (foo || {}).barumumnya tidak akan dikompilasi dalam naskah karena {}tidak akan sama dengan foo. Itulah masalah yang ingin dihindari oleh solusi @ VeganHunter.
Simon_Weaver
1
@Simon_Weaver lalu (foo || {bar}). Bar akan membiarkan kompiler berjalan dengan lancar dan saya pikir verbositas itu dapat diterima.
Harps
@harps sebenarnya ini hanya mengkompilasi jika bar didefinisikan sebagai variabel, yang kemungkinan besar tidak akan menjadi
Simon_Weaver
83

Perbarui: Ya didukung sekarang!

Itu baru saja dirilis dengan TypeScript 3.7: https://devblogs.microsoft.com/typescript/announcing-typescript-3-7/

Ini disebut perangkaian opsional : https://devblogs.microsoft.com/typescript/announcing-typescript-3-7/#optional-chaining

Dengan itu sebagai berikut:

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

setara dengan:

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

Jawaban lama

Ada permintaan fitur terbuka untuk ini di github di mana Anda dapat menyuarakan pendapat / keinginan Anda: https://github.com/Microsoft/TypeScript/issues/16

basarat
sumber
36

Sunting 13 November 2019!

Pada 5 November 2019, TypeScript 3.7 telah dikirimkan dan sekarang mendukung ?. operator perangkaian opsional 🎉🎉🍾🍾🎉 !!!

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


Hanya Untuk Tujuan Sejarah:

Sunting: Saya telah memperbarui jawabannya berkat komentar fracz.

TypeScript 2.0 dirilis !.Ini tidak sama dengan ?.(Safe Navigator di C #)

Lihat jawaban ini untuk lebih jelasnya:

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

Ini hanya akan memberi tahu kompiler bahwa nilainya tidak nol atau tidak terdefinisi. Ini tidak akan memeriksa apakah nilainya nol atau tidak terdefinisi.

Operator pernyataan 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
}
Jose A
sumber
4
Tidak sama dengan ?karena itu menegaskan bahwa nilainya didefinisikan. ?diharapkan gagal / mengevaluasi secara diam-diam ke false. Ngomong-ngomong, senang tahu.
fracz
1
Sekarang saya memikirkannya ... Jawaban ini tidak ada gunanya, karena tidak melakukan "navigasi aman" seperti yang dilakukan operator C #.
Jose A
5
Ini menjawab pertanyaan saya. Saya tahu tentang? dari c # dan mencobanya dalam naskah. Itu tidak berhasil, tetapi saya melihat itu! ada tetapi tidak tahu apa yang dilakukannya. Saya bertanya-tanya apakah itu sama, melakukan pencarian google, dan menemukan jalan saya ke pertanyaan ini yang memberitahu saya bahwa tidak, mereka berbeda.
Llewey
11

Elvis (?.) Operator Chaining Opsional didukung dalam TypeScript 3.7.

Anda dapat menggunakannya untuk memeriksa nilai nol: cats?.miowsmengembalikan nol jika kucing nol atau tidak terdefinisi.

Anda juga dapat menggunakannya untuk pemanggilan metode opsional: cats.doMiow?.(5)akan memanggil doMiow jika ada.

Akses properti juga mungkin: cats?.['miows'].

Referensi: https://devblogs.microsoft.com/typescript/announcing-typescript-3-7-beta/

superluminary
sumber
Tolong koreksi saya, tetapi operator Elvis setidaknya di Kotlin ?:. Apakah Anda punya referensi?
rekire
@rekire: devblogs.microsoft.com/typescript/…
superluminary
1
Ini akan segera didukung dalam JS polos - developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Mottie
1
Pengumuman rilis TypeScript 3.7 menyebutkannya: devblogs.microsoft.com/typescript/announcing-typescript-3-7
György Balássy
10

Operator ?.tidak didukung di TypeScript versi 2.0 .

Jadi saya menggunakan fungsi berikut:

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

penggunaannya terlihat seperti ini:

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

plus, Anda dapat menetapkan nilai default:

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

Ini bekerja sangat baik dengan IntelliSense di Visual Studio.

VeganHunter
sumber
1
Ini persis apa yang saya cari! Ia bekerja dalam naskah 2.1.6.
Rajab Shakirov
5
atau Anda bisa menyebutnya elvis<T>;-)
Simon_Weaver
3
Simon_Weaver, saya menyebutnya "badut sedih": o (
VeganHunter
5

Akhirnya di sini!

Berikut ini beberapa contoh:

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

Tetapi itu tidak bekerja persis sama dengan asumsi Anda.

Alih-alih mengevaluasi

foo?.bar

untuk potongan kode kecil ini kita semua terbiasa menulis

foo ? foo.bar : null

sebenarnya dievaluasi untuk

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

yang bekerja untuk semua nilai falsey seperti string kosong, 0 atau false.

Saya hanya tidak memiliki penjelasan mengapa mereka tidak mengkompilasinya foo == null

zoran404
sumber
3

Kami menciptakan metode util ini saat bekerja pada Phonetradr yang dapat memberi Anda akses tipe-aman ke properti-properti yang dalam dengan Typescript:

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

phidias
sumber
Keren!! Apakah ada peringatan? Saya memiliki kelas pembungkus dengan sekitar 20 getter untuk ditulis, masing-masing memiliki jenis pengembalian berikut - dan semua bidang harus dicentang nolreturn this.entry.fields.featuredImage.fields.file.url;
Drenai
Satu-satunya peringatan mungkin adalah dampak kinerja, tapi saya tidak memenuhi syarat untuk berbicara tentang bagaimana berbagai JITers menangani ini.
Ray Suelzer
2

Saya biasanya tidak merekomendasikan pendekatan ini (hati-hati karena masalah kinerja), tetapi Anda dapat menggunakan operator spread untuk menduplikasi objek yang dangkal, yang kemudian dapat Anda akses ke properti.

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

Ini berfungsi karena jenis 'nama depan' adalah 'diperbanyak' melalui.

Saya akan menggunakan ini paling sering ketika saya memiliki find(...)ekspresi yang dapat mengembalikan nol dan saya membutuhkan satu properti dari itu:

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

Mungkin ada beberapa kasus tepi dengan cara jenis naskah menyimpulkan jenis dan ini tidak akan dikompilasi, tetapi ini umumnya akan bekerja.

Simon_Weaver
sumber
2

Ini disebut perangkaian opsional dan Ada di dalam Script 3.7

Chaining opsional memungkinkan kita menulis kode di mana kita dapat segera berhenti menjalankan beberapa ekspresi jika kita mengalami nol atau tidak terdefinisi

Damian Green
sumber
0

Seperti dijawab sebelumnya, saat ini masih dipertimbangkan tetapi sudah mati di air selama beberapa tahun sekarang.

Berdasarkan jawaban yang ada, inilah versi manual paling ringkas yang dapat saya pikirkan:

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'

Secara diam-diam gagal pada kesalahan properti yang hilang. Itu kembali ke sintaks standar untuk menentukan nilai default, yang dapat dihilangkan sepenuhnya juga.


Meskipun ini berfungsi untuk kasus-kasus sederhana, jika Anda memerlukan hal-hal yang lebih kompleks seperti memanggil fungsi dan kemudian mengakses properti pada hasilnya, maka kesalahan lainnya juga ditelan. Desain yang buruk.

Dalam kasus di atas, versi yang dioptimalkan dari jawaban lain yang diposting di sini adalah opsi yang lebih baik:

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'

Contoh yang lebih kompleks:

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

Anda juga dapat pergi ke arah lain dan menggunakan sesuatu seperti Lodash ' _.get(). Itu ringkas, tetapi kompiler tidak akan dapat menilai validitas properti yang digunakan:

console.log(_.get(obj1, 'a.b.c'));
Benny Bottema
sumber
0

Belum (per September, 2019), tetapi karena "operator navigasi yang aman" sekarang pada Tahap 3 , itu sedang dilaksanakan di TypeScript.

Tonton masalah ini untuk pembaruan:

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

Beberapa mesin memiliki implementasi awal:

JSC: https://bugs.webkit.org/show_bug.cgi?id=200199

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

SM: https://bugzilla.mozilla.org/show_bug.cgi?id=1566143

(via https://github.com/tc39/proposal-optional-chaining/issues/115#issue-475422578 )

Anda dapat menginstal plugin untuk mendukungnya sekarang:

npm install --save-dev ts-optchain

Di tsconfig.json Anda:

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

Saya berharap jawaban ini kedaluwarsa dalam 6 bulan ke depan, tetapi semoga ini akan membantu seseorang untuk sementara waktu.

Jamon Holmgren
sumber
-1

_.get(obj, 'address.street.name')berfungsi bagus untuk JavaScript di mana Anda tidak memiliki tipe. Tetapi untuk TypeScript kita membutuhkan operator Elvis yang asli!

Angelos Pikoulas
sumber