Aturan di bawah ini adalah aturan yang paling banyak mencegah bug di kode TypeScript sungguhan. Masing-masing menunjukkan versi yang umum lebih dulu, lalu versi yang lebih baik, dalam kode yang bisa kamu jalankan. Aturan pertama yang paling penting: berhenti memakai any.
Pakai unknown, Bukan any
any mematikan pemeriksaan tipe untuk sebuah nilai dan semua yang dihitung darinya. unknown juga menerima nilai apa pun, tetapi kamu wajib memeriksanya sebelum dipakai, sehingga pemeriksaan terjadi di tempat data masuk ke programmu:
Batasnya adalah tempat-tempat di mana tipe tidak lagi dijamin: JSON.parse, respons fetch, localStorage, input form, environment variable, dan pesan dari proses lain. Validasi di sana dengan type guard atau library skema, dan sisa kodenya bisa memercayai tipenya.
Biarkan strict Menyala
strict adalah default di TypeScript 7; jangan dimatikan. Tambahkan pemeriksaan yang tidak tercakup olehnya tetapi paling banyak menangkap bug:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": true
}
}
noUncheckedIndexedAccess membuat arr[i] dan record[key] menyertakan undefined, yang memang dikembalikan untuk index yang tidak ada. noImplicitOverride mewajibkan method subclass menulis override, dan noFallthroughCasesInSwitch menolak case yang langsung berlanjut ke case berikutnya.
Biarkan Inferensi Bekerja
Beri anotasi pada hal yang tidak bisa diketahui TypeScript: parameter fungsi, dan tipe kembalian fungsi yang dipakai modul lain. Serahkan variabel lokal dan parameter callback ke inferensi. Anotasi yang tidak perlu bukan sekadar mengganggu; anotasi itu bisa membuat tipe lebih lebar daripada nilainya:
Tanpa komentar @ts-expect-error, setStatus(annotated) menghasilkan error TS2345. const yang disimpulkan tetap memegang tipe literal "active", jadi diterima. Arahkan kursor ke variabel di editormu untuk melihat hasil inferensinya sebelum kamu menambahkan tipe.
Pilih Union Type daripada Enum
Union berisi string literal memberikan autocomplete dan pemeriksaan menyeluruh tanpa kode yang dihasilkan. Kalau kamu juga butuh daftar nilainya saat runtime, turunkan tipenya dari array as const:
const ROLES = ["admin", "editor", "viewer"] as const;
type Role = (typeof ROLES)[number]; // "admin" | "editor" | "viewer"
function canEdit(role: Role): boolean {
return role !== "viewer";
}
console.log(canEdit("editor")); // true
console.log(ROLES.filter(canEdit)); // [ 'admin', 'editor' ]
canEdit("owner"); // error TS2345: Argument of type '"owner"' is not assignable to parameter of type '"admin" | "editor" | "viewer"'.
enum Role { Admin, Viewer } dikompilasi menjadi objek dengan reverse mapping, dan parameter enum numerik menerima variabel number apa pun, bahkan yang berisi nilai yang tidak tercantum di enum. Enum juga tidak bisa berjalan dengan type stripping milik Node (TypeScript enum is not supported in strip-only mode). Untung ruginya dibandingkan di halaman enum.
Periksa Objek Config dengan satisfies
Memberi anotasi tipe yang lebar seperti Record<string, Route> pada sebuah objek memang memeriksa nilainya, tetapi melupakan key-nya. satisfies memeriksa hal yang sama dan tetap menyimpan tipe persisnya:
Pakai anotasi kalau variabel itu harus punya tipe persis seperti yang dideklarasikan (parameter fungsi, nilai yang akan kamu beri nilai ulang). Pakai satisfies untuk tabel lookup, peta route, token tema, dan objek konstan lainnya.
Modelkan State dengan Discriminated Union
Satu objek dengan field opsional mengizinkan state yang mustahil terjadi: loading: true bersamaan dengan error, atau data yang tidak ada setelah sukses. Union berisi objek dengan tag bersama hanya mengizinkan state yang nyata, dan setiap cabang hanya melihat field miliknya sendiri:
Baris never adalah pemeriksaan menyeluruh. Kalau seseorang menambahkan state dan lupa menanganinya, build gagal di baris itu:
Error-nya adalah index.ts(14,13): error TS2322: Type '{ status: "cancelled"; }' is not assignable to type 'never'. Error ini menyebutkan case yang belum ditangani. Tambahkan case "cancelled": dan kodenya bisa dikompilasi.
Hindari ! dan as
Non-null assertion x! dan type assertion x as T menyuruh compiler berhenti memeriksa. Keduanya tidak mengubah nilai saat runtime, jadi assertion yang salah berubah menjadi crash di kemudian hari, jauh dari penyebabnya:
Ganti ! dengan ?., ??, return lebih awal, atau error yang dilempar dengan pesan yang berguna. Ganti as dengan type guard yang benar-benar menguji nilainya. Satu-satunya assertion yang selalu aman adalah as const, karena hanya membuat tipe lebih sempit dan read-only. Pengaturan lint yang baik (no-non-null-assertion dan no-explicit-any dari typescript-eslint) menandai sisanya.
Jadikan Data readonly
Tandai property dan array dengan readonly kalau kode tidak boleh mengubahnya. Compiler lalu menolak push, sort, dan penugasan, dan fungsi mengembalikan nilai baru alih-alih mengubah input-nya:
readonly hanya diperiksa saat kompilasi dan hanya sedalam satu tingkat: modifier ini tidak membekukan objek saat runtime. Meski begitu, itu sudah cukup untuk menangkap perubahan tidak sengaja pada state bersama, penyebab sebagian besar bug jenis ini.
Pertanyaan yang sering diajukan
Apakah sebaiknya aku memakai any di TypeScript?
Hampir tidak pernah di kode aplikasi. any mematikan pemeriksaan untuk nilai itu dan semua yang diturunkan darinya. Pakai unknown untuk nilai yang tipenya belum kamu ketahui, lalu persempit dengan pemeriksaan; simpan any untuk jalan pintas yang jarang, dengan komentar yang menjelaskan alasannya.
Apakah aku harus memberi anotasi pada setiap variabel di TypeScript?
Tidak. Biarkan TypeScript menyimpulkan variabel lokal dan parameter callback. Beri anotasi pada parameter fungsi (tidak bisa disimpulkan) dan tipe kembalian fungsi yang di-export, supaya perubahan di dalam fungsi tidak diam-diam mengubah tipe publiknya.
Apakah enum TypeScript termasuk praktik yang buruk?
Tidak salah, tetapi banyak tim menghindarinya. Enum menghasilkan kode saat runtime, tidak bisa berjalan dengan type stripping milik Node, dan parameter enum numerik menerima variabel number apa pun, apa pun nilainya. Union berisi string literal, atau array as const dengan tipe turunan, memberikan autocomplete dan pemeriksaan yang sama tanpa kode yang dihasilkan.
Kapan sebaiknya aku memakai type assertion dengan as?
Hanya ketika kamu tahu sesuatu yang tidak bisa diketahui compiler, dan sebaiknya tepat setelah pemeriksaan yang membuktikannya. as tidak mengubah nilai apa pun saat runtime, jadi {} as User lolos kompilasi lalu tidak punya name. Type guard yang menguji nilainya adalah alat yang lebih aman di sebagian besar kasus.
Pengaturan tsconfig apa yang terbaik untuk proyek TypeScript baru?
Biarkan strict menyala (default di TypeScript 7) dan tambahkan noUncheckedIndexedAccess. Banyak proyek juga menyalakan noImplicitOverride, noFallthroughCasesInSwitch, dan verbatimModuleSyntax. Config yang ditulis tsc --init mengatur strict, noUncheckedIndexedAccess, exactOptionalPropertyTypes, dan verbatimModuleSyntax, di antara yang lain.