Menu

Tanda Seru (!) di TypeScript: Non-Null Assertion Operator

Tanda seru setelah sebuah nilai, seperti user!, adalah operator non-null assertion: ia membuang null dan undefined dari tipe tanpa pemeriksaan apa pun saat runtime. Pelajari apa yang dilakukan x!, bentuk definite assignment let x!: T dan prop!: T, kenapa keduanya berisiko, dan alternatif yang lebih aman.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

Tanda seru setelah sebuah ekspresi, value!, adalah operator non-null assertion. Operator ini membuang null dan undefined dari tipe, jadi number | undefined bisa dipakai sebagai number. Ini adalah janji kepada compiler, bukan pemeriksaan: saat runtime tidak terjadi apa-apa.

Tanpa !, tea.toFixed(2) adalah error TS18048, 'tea' is possibly 'undefined'. Dengan tanda itu, kodenya lolos kompilasi karena kamu sudah memberi tahu compiler bahwa key-nya ada.

Apa yang Dilakukan x! saat Runtime: Tidak Ada

! dihapus dari output. JavaScript hasil kompilasi untuk prices.get("coffee")! hanyalah prices.get("coffee"). Kalau assertion-nya salah, error-nya muncul belakangan, di tempat pertama nilai yang hilang itu dipakai:

Program mencetak Cannot read properties of undefined (reading 'toFixed'). Crash-nya terjadi di baris setelah !, dan di kode sungguhan jaraknya bisa jauh lebih besar: undefined itu bisa saja disimpan di sebuah object lalu meledak di file lain. Jarak itulah yang membuat ! berisiko.

Definite Assignment: let x!: T

Simbol yang sama di sebuah deklarasi punya arti yang berkaitan. TypeScript melacak apakah sebuah variabel sudah di-assign sebelum dibaca, dan ia tidak bisa mengikuti assignment yang dilakukan di dalam fungsi lain:

index.ts(9,13): error TS2454: Variable 'config' is used before being assigned.

let config!: { port: number }; adalah definite assignment assertion: "variabel ini akan di-assign sebelum dibaca". Cara ini memperbaiki error-nya, dengan jebakan yang sama seperti x!: kalau init() suatu saat terlewat, pembacaannya mendapat undefined. Menyusun ulang kode biasanya lebih baik, misalnya const config = init(); dengan init mengembalikan object-nya.

Properti Class: prop!: T

Dengan strictPropertyInitialization (bagian dari strict), setiap properti class harus diinisialisasi di deklarasinya atau di constructor. Kalau tidak, kamu mendapat TS2564: Property 'socket' has no initializer and is not definitely assigned in the constructor. Kalau sebuah properti baru diatur belakangan oleh method atau framework, prop!: T memberi tahu compiler untuk menerimanya:

console.log pertama menunjukkan celahnya: tipenya menyatakan socket selalu ada, tetapi sebelum open() nilainya undefined. Memanggil c.socket.send pada saat itu lolos kompilasi dan melempar error. Kalau properti itu memang bisa tidak ada, deklarasikan sebagai socket?: ... lalu periksa, atau buat object-nya di constructor. Tempat utama prop! menjadi praktik standar adalah framework yang mengisi properti setelah object dibuat: @ViewChild(...) child!: ChildDirective di Angular (diatur sebelum ngAfterViewInit berjalan), atau class entity ORM yang kolomnya diisi library saat memuat sebuah baris (dokumentasi MikroORM menulis @Property() title!: string).

Alternatif yang Lebih Aman

Sebagian besar ! bisa diganti dengan sesuatu yang diperiksa compiler, atau dengan pemeriksaan yang gagal secara jelas di tempat yang tepat:

Kedua helper itu mempersempit tipe seperti yang dilakukan !, tetapi asumsi yang salah langsung menghasilkan missing HOST di tempat itu juga, bukan TypeError di tempat lain. assertDefined adalah assertion function (asserts value is ...): setelah dipanggil, compiler memperlakukan host sebagai string. Pola lainnya ada di type guard.

DaripadaTulisYang terjadi kalau nilainya tidak ada
user!.nameif (user) { user.name }bloknya dilewati
user!.nameuser?.nameundefined
count!count ?? 0nilai default dipakai
map.get(k)!must(map.get(k), "k")error yang jelas di baris itu
let x!: Tconst x = compute()tidak ada yang bisa salah

Tanda Seru Lainnya

! punya arti berbeda tergantung letaknya:

KodeArti
value! (setelah ekspresi)non-null assertion, khusus TypeScript
let x!: T, prop!: Tdefinite assignment assertion, khusus TypeScript
!value (sebelum ekspresi)NOT logika, JavaScript biasa
!!valuemengubah menjadi boolean, JavaScript biasa
a !== b, a != bketidaksamaan, JavaScript biasa

Hanya dua yang pertama yang dihapus saat kompilasi. !value dan !!value dijalankan saat runtime dan mengembalikan boolean.

Pertanyaan yang sering diajukan

Apa arti tanda seru setelah variabel di TypeScript?

value! adalah operator non-null assertion. Operator ini memberi tahu compiler bahwa value bukan null atau undefined, jadi tipenya kehilangan kedua member itu: string | undefined menjadi string. Operator ini dihapus dari JavaScript yang dihasilkan dan tidak menambahkan pemeriksaan apa pun saat runtime, jadi kalau dugaanmu salah, program akan gagal belakangan dengan TypeError.

Apa perbedaan ! dan ? di TypeScript?

x! menegaskan bahwa nilainya ada dan memberimu tipe non-null, tanpa pemeriksaan. x?.y memeriksa saat runtime: kalau x bernilai null atau undefined, ia berhenti dan mengembalikan undefined. Di sebuah deklarasi, name?: string membuat properti menjadi opsional, sedangkan name!: string menyatakan bahwa properti wajib itu akan di-assign di tempat yang tidak bisa dilihat compiler.

Apa arti let x!: string?

Itu adalah definite assignment assertion. Ia memberi tahu compiler bahwa variabel akan di-assign sebelum dibaca, walaupun compiler tidak bisa membuktikannya (misalnya, assignment-nya terjadi di dalam fungsi lain). Tanpa itu, membaca variabelnya adalah error TS2454, Variable 'x' is used before being assigned.

Bagaimana cara memperbaiki "has no initializer and is not definitely assigned in the constructor"?

Itu adalah error TS2564 dari strictPropertyInitialization. Beri properti itu nilai awal, assign di dalam constructor, jadikan opsional (prop?: T), atau, kalau sebuah framework atau method init memang mengaturnya sebelum dipakai, tulis prop!: T. ! adalah pilihan terakhir karena tidak ada yang memeriksa janji itu.

Apakah operator non-null assertion termasuk praktik yang buruk?

Tidak salah, tetapi setiap ! adalah klaim yang tidak diperiksa. Pengaturan lint seperti @typescript-eslint/no-non-null-assertion menandainya. Lebih baik pakai pemeriksaan yang melakukan narrowing (if (x), x ?? fallback, x?.y) atau helper yang melempar error yang jelas. Simpan ! untuk tempat yang nilainya dijamin oleh logika yang tidak bisa diikuti compiler.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai