Praktik Terbaik dalam Pemrograman TypeScript

Praktik Terbaik dalam Pemrograman TypeScript

Artikel ini menjelaskan praktik terbaik dalam pemrograman TypeScript.

Panduan ini menjelaskan praktik terbaik secara praktis untuk memanfaatkan tipe TypeScript guna mengurangi bug dan menulis kode yang lebih mudah dibaca.

YouTube Video

Praktik Terbaik dalam Pemrograman TypeScript

Manfaat terbesar dari TypeScript adalah 'mencegah bug dengan tipe dan membuat maksud kode menjadi jelas'.

Praktik terbaik bukan sekadar aturan, melainkan seperangkat prinsip untuk menulis kode yang aman, mudah dibaca, dan mudah dipelihara. Di bawah ini, kami memperkenalkan praktik terbaik TypeScript yang sering digunakan beserta contoh praktisnya.

Hindari penggunaan any dan selalu berikan definisi tipe yang bermakna

Pertama, mari kita lihat poin tentang 'menghindari penggunaan any dan memberikan tipe yang bermakna.'.

any sepenuhnya menonaktifkan pemeriksaan tipe, yang menghilangkan tujuan utama penggunaan TypeScript. Daripada menggunakan any hanya agar sesuatu dapat berjalan, lebih penting untuk menyediakan tipe yang sejelas mungkin.

1// Bad
2function parse(data: any) {
3  return data.value;
4}

Kode ini dapat menerima nilai apa pun, sehingga error pada waktu runtime tidak dapat dicegah.

1// Good
2type ParsedData = {
3  value: string;
4};
5
6function parse(data: ParsedData): string {
7  return data.value;
8}

Dengan mendefinisikan tipe, Anda memperjelas maksud dari input dan output dan meningkatkan keamanan.

Selalu definisikan struktur objek secara eksplisit menggunakan type atau interface.

Selanjutnya, mari kita lihat poin tentang 'selalu mendefinisikan struktur objek secara eksplisit menggunakan type atau interface.'.

Jika Anda menggunakan objek secara ad-hoc, strukturnya dapat menjadi ambigu. Selalu ekstrak tipe untuk meningkatkan reusabilitas dan kemudahan pemeliharaan.

1// Bad
2function createUser(user: { name: string; age: number }) {
3  console.log(user.name);
4}

Bahkan dalam potongan kode yang kecil, penting untuk membiasakan diri memisahkan tipe.

1// Good
2type User = {
3  name: string;
4  age: number;
5};
6
7function createUser(user: User): void {
8  console.log(user.name);
9}

Dengan memberi nama pada tipe, pemahaman keseluruhan basis kode menjadi jauh lebih mudah.

Gunakan union type untuk mewakili semua kemungkinan keadaan secara tepat.

Jika Anda menggunakan tipe string atau number mentah untuk kondisi, nilai tak terduga dapat lolos. Dengan menggunakan tipe union, Anda dapat merepresentasikan hanya keadaan yang diperbolehkan pada tingkat tipe.

1// Bad
2function setStatus(status: string) {
3  console.log(status);
4}

Dalam kode ini, string yang tidak bermakna atau nilai yang salah tidak dapat dideteksi pada waktu kompilasi.

1// Good
2type Status = "idle" | "loading" | "success" | "error";
3
4function setStatus(status: Status): void {
5  console.log(status);
6}

Dengan menggunakan tipe union, Anda dapat secara andal menghilangkan 'keadaan yang tidak mungkin' pada waktu kompilasi. Sebagai hasilnya, keamanan percabangan kondisi dan keandalan kode akan meningkat.

Tangani null dan undefined secara eksplisit.

Berikutnya, mari kita lihat poin tentang 'menangani null dan undefined secara eksplisit.'.

Dalam TypeScript, penting untuk mengungkapkan kemungkinan ketiadaan nilai dalam tipe. Jika Anda membiarkan sesuatu tetap ambigu, hal itu dapat menyebabkan error pada waktu runtime.

1type User = {
2  name: string;
3  email?: string;
4};

email mungkin tidak ada, jadi Anda harus menanganinya dengan asumsi tersebut.

1function printEmail(user: User): void {
2  if (user.email) {
3    console.log(user.email);
4  }
5}

Selalu periksa nilai opsional sebelum menggunakannya.

Gunakan type assertion (as) hanya sebagai upaya terakhir.

Selanjutnya, mari kita lihat poin tentang 'tidak terlalu sering menggunakan type assertion.'.

Tipe asersi sementara melewati pemeriksaan tipe TypeScript untuk menyatakan, 'Saya tahu nilai ini bertipe ini.'. Terlalu sering menggunakannya dapat mengurangi keamanan tipe.

1// Bad
2const value = input as string;

Dalam kode ini, bahkan jika nilai sebenarnya bukan string, tidak terjadi kesalahan, yang dapat menyebabkan kesalahan saat dijalankan. Seperti yang ditunjukkan pada kode berikut, pilihlah untuk memeriksa dengan aman menggunakan type guard terlebih dahulu.

1// Good
2function isString(value: unknown): value is string {
3  return typeof value === "string";
4}
5
6if (isString(input)) {
7  console.log(input.toUpperCase());
8}

Type guard adalah mekanisme untuk menentukan tipe dengan aman saat memeriksa nilai sebenarnya. Dengan memprioritaskan type guard dibandingkan type assertion, Anda dapat lebih mudah mencegah kesalahan saat dijalankan.

Jangan terlalu mengandalkan inferensi tipe untuk tipe pengembalian.

Selanjutnya, mari kita lihat poin tentang 'tidak terlalu bergantung pada inferensi untuk tipe nilai kembali (return type).'.

Inferensi tipe TypeScript memang kuat, tetapi lebih aman untuk secara eksplisit menuliskan tipe pengembalian fungsi publik. Hal ini meminimalkan dampak potensial dari perubahan di masa depan.

1// Bad
2function sum(a: number, b: number) {
3  return a + b;
4}

Nyatakan maksud Anda dengan jelas bahkan pada fungsi kecil.

1// Good
2function sum(a: number, b: number): number {
3  return a + b;
4}

Menuliskan tipe pengembalian meningkatkan stabilitas API Anda.

Tangani input dengan aman menggunakan unknown.

Selanjutnya, mari kita lihat poin tentang 'menerima input eksternal dengan aman menggunakan unknown.'.

Untuk masukan eksternal seperti API, JSON, atau masukan pengguna, gunakan unknown daripada any. Dengan melakukan hal ini, Anda memastikan semua nilai telah divalidasi, sehingga menjaga keamanan tipe.

1// Bad
2function handleResponse(data: any) {
3  console.log(data.id);
4}

Berikut adalah cara memvalidasi tipe dengan unknown.

 1// Good
 2function handleResponse(data: unknown): void {
 3  if (
 4    typeof data === "object" &&
 5    data !== null &&
 6    "id" in data
 7  ) {
 8    console.log((data as { id: number }).id);
 9  }
10}

unknown tidak dapat digunakan begitu saja; tipe ini memerlukan validasi terlebih dahulu. Ini sangat efektif ketika menangani masukan eksternal.

Tingkatkan daya ekspresif dengan menggabungkan tipe-tipe kecil.

Selanjutnya, mari kita lihat poin tentang 'meningkatkan ekspresivitas dengan menggabungkan tipe-tipe kecil.'.

Mendefinisikan tipe besar sekaligus mengurangi keterbacaan dan kemudahan pemeliharaan. Bagi tipe Anda menjadi unit yang berarti dan gabungkan sesuai kebutuhan.

 1type Id = number;
 2
 3type UserProfile = {
 4  id: Id;
 5  name: string;
 6};
 7
 8type UserWithStatus = UserProfile & {
 9  status: "active" | "inactive";
10};

Menganggap tipe sebagai komponen membantu mengorganisasi desain Anda.

type dan interface

Keuntungan interface

type dan interface keduanya bisa mendefinisikan tipe, tetapi penggunaan dan karakteristiknya berbeda. Dengan menggunakan keduanya sesuai peran yang tepat, maksud dari definisi tipe Anda menjadi lebih jelas.

 1// Bad
 2type User = {
 3  id: number;
 4  name: string;
 5};
 6
 7type AdminUser = {
 8  id: number;
 9  name: string;
10  role: "admin";
11};

Jika Anda menduplikasi bagian umum seperti ini, kode Anda menjadi rentan terhadap perubahan.

1// Good
2interface User {
3  id: number;
4  name: string;
5}
6
7interface AdminUser extends User {
8  role: "admin";
9}

interface ideal untuk desain yang melibatkan ekstensi (extends) dan paling cocok untuk mengekspresikan 'bentuk' objek.

Keuntungan type

Di sisi lain, type lebih ekspresif dan cocok untuk menangani union dan intersection type.

1// Good
2type Status = "idle" | "loading" | "success" | "error";
3
4type ApiResponse<T> =
5  | { status: "success"; data: T }
6  | { status: "error"; message: string };

type sangat cocok untuk mengekspresikan status, opsi, dan kombinasi.

Pedoman memilih antara type dan interface

Sebagai pedoman umum, gunakan interface untuk struktur dan kontrak objek, serta type jika Anda memerlukan ekspresi union, intersection, atau operasi tipe lainnya.

Keduanya bekerja serupa, namun penting untuk memilih berdasarkan mana yang paling dapat menyampaikan alasan keberadaan tipe tersebut.

Perlakukan tipe Anda sebagai dokumentasi.

Akhirnya, mari kita lihat poin tentang 'menulis tipe sebagai dokumentasi.'.

Definisi tipe yang baik mampu menyampaikan informasi lebih banyak daripada komentar. Penting untuk mengupayakan keadaan di mana 'spesifikasi dapat dipahami hanya dengan melihat tipe-tipe yang digunakan.'.

1type ApiError = {
2  code: number;
3  message: string;
4  retryable: boolean;
5};

Dengan cara ini, salah satu keunggulan utama TypeScript adalah bahwa definisi tipe dapat berfungsi sebagai semacam dokumen spesifikasi.

Ringkasan

Praktik terbaik TypeScript bukan tentang menjadi terlalu ketat. Inti utamanya adalah memperjelas maksud melalui tipe dan menulis kode yang tangguh terhadap perubahan.

Dengan mengumpulkan aturan-aturan kecil dalam pengembangan sehari-hari, Anda bisa mendapatkan efek jangka panjang seperti 'review lebih mudah', 'lebih sedikit bug', dan 'pemahaman lebih baik untuk diri Anda di masa depan dan orang lain.'.

Pertama, dengan berpikir 'bagaimana saya bisa mengekspresikan ini dengan tipe?', Anda akan menulis kode berkualitas tinggi dengan gaya TypeScript.

Anda dapat mengikuti artikel di atas menggunakan Visual Studio Code di saluran YouTube kami. Silakan periksa juga saluran YouTube kami.

YouTube Video