TypeScript untuk Pemula: Panduan Lengkap
TypeScript adalah JavaScript yang diberi sistem tipe statis. Kode TypeScript tetap dikompilasi menjadi JavaScript, tetapi sebelum dijalankan kita mendapatkan bantuan pemeriksaan tipe. Bantuan ini sangat berguna ketika aplikasi mulai memiliki banyak file, banyak data, dan banyak orang yang ikut mengubah kode.
Tujuan TypeScript bukan membuat kode menjadi lebih rumit. Tujuannya adalah membuat kontrak antarbagian aplikasi lebih jelas: function menerima apa, object memiliki field apa, API mengembalikan bentuk data seperti apa, dan kesalahan apa yang dapat dicegah sebelum kode sampai ke browser atau server.
Tujuan Belajar
Setelah membaca tutorial ini, kamu diharapkan mampu:
- Menulis tipe dasar untuk variable, parameter, dan return value.
- Membuat tipe object dengan
typedaninterface. - Menggunakan union type untuk nilai yang memiliki beberapa kemungkinan bentuk.
- Melakukan narrowing agar TypeScript memahami cabang logika.
- Menggunakan generics untuk function dan struktur data yang fleksibel.
- Menerapkan utility types seperti
Pick,Omit,Partial, danReadonly. - Mendesain tipe sederhana untuk data API.
Mengapa TypeScript Berguna?
JavaScript sangat fleksibel. Fleksibilitas itu menyenangkan saat belajar, tetapi dapat menjadi sumber bug ketika aplikasi membesar. Contoh sederhana:
function formatPrice(price) {
return price.toLocaleString("id-ID");
}
formatPrice(15000);
formatPrice("15000");
formatPrice(null);Kode ini mungkin terlihat aman, tetapi null akan membuat error saat runtime. Dengan TypeScript, kita bisa menyatakan bahwa price harus berupa number.
function formatPrice(price: number): string {
return price.toLocaleString("id-ID");
}
formatPrice(15000);
// formatPrice(null); // Error sebelum aplikasi dijalankanKesalahan yang lebih cepat ditemukan biasanya lebih murah diperbaiki.
Tipe Primitif
Tipe primitif yang sering dipakai adalah string, number, boolean, null, dan undefined.
const title: string = "Belajar TypeScript";
const durationMinutes: number = 90;
const isPublished: boolean = true;Pada banyak kasus, TypeScript bisa melakukan inference, yaitu menebak tipe dari nilai awal.
const title = "Belajar TypeScript"; // string
const durationMinutes = 90; // number
const isPublished = true; // booleanGunakan anotasi tipe saat tipe tidak jelas dari nilai awal, terutama parameter function dan data kosong.
const tags: string[] = [];
function addTag(tag: string) {
tags.push(tag);
}Array dan Tuple
Array dipakai untuk daftar dengan tipe elemen yang sama.
const scores: number[] = [80, 90, 75];
const names: Array<string> = ["Ayu", "Bima", "Citra"];Tuple dipakai saat posisi elemen punya makna khusus.
const coordinate: [number, number] = [-6.889, 109.675];
const httpResult: [number, string] = [200, "OK"];Tuple sebaiknya digunakan secukupnya. Jika jumlah field bertambah atau butuh nama yang jelas, object biasanya lebih mudah dibaca.
Object, Type Alias, dan Interface
Untuk object, kita bisa memakai type atau interface.
type User = {
id: string;
name: string;
email: string;
role: "admin" | "user";
};
function getDisplayName(user: User): string {
return `${user.name} <${user.email}>`;
}interface juga umum dipakai untuk struktur object.
interface Course {
id: string;
title: string;
lessonCount: number;
published: boolean;
}
const course: Course = {
id: "course-1",
title: "Dasar TypeScript",
lessonCount: 12,
published: true,
};Keduanya valid. Untuk pemula, pilih salah satu dan konsisten di satu project. Banyak tim memakai type untuk union/komposisi dan interface untuk object yang dapat diperluas.
Optional Property dan Nullable
Optional property ditandai dengan ?. Artinya field boleh tidak ada.
type Profile = {
id: string;
name: string;
avatarUrl?: string;
};Nullable berarti field ada, tetapi nilainya boleh null.
type Article = {
id: string;
title: string;
publishedAt: Date | null;
};Perbedaan ini penting saat bekerja dengan API dan database. undefined sering berarti belum disediakan, sedangkan null sering berarti sengaja kosong.
Union Type
Union type menyatakan nilai dapat berupa salah satu dari beberapa tipe.
type Status = "draft" | "published" | "archived";
function getStatusLabel(status: Status): string {
if (status === "draft") return "Draft";
if (status === "published") return "Terbit";
return "Arsip";
}Union lebih aman daripada string bebas karena TypeScript dapat menolak nilai yang tidak termasuk daftar.
// const status: Status = "deleted"; // Error
const status: Status = "published";Narrowing dengan typeof dan in
Ketika sebuah nilai memiliki beberapa kemungkinan tipe, TypeScript perlu bukti sebelum kita memakai method tertentu.
function normalizeId(id: string | number): string {
if (typeof id === "number") {
return id.toString();
}
return id.trim();
}Untuk object union, operator in dapat membantu.
type SuccessResponse = { ok: true; data: string[] };
type ErrorResponse = { ok: false; message: string };
type ApiResponse = SuccessResponse | ErrorResponse;
function renderResponse(response: ApiResponse): string {
if (response.ok) {
return `Total data: ${response.data.length}`;
}
return `Error: ${response.message}`;
}Property ok menjadi pembeda bentuk data. Pola ini disebut discriminated union dan sangat berguna untuk state UI.
State UI dengan Discriminated Union
Daripada membuat banyak boolean seperti isLoading, isError, dan isSuccess, kita bisa memakai union.
type LoadState<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; message: string };
function getMessage(state: LoadState<string[]>): string {
switch (state.status) {
case "idle":
return "Belum memuat data";
case "loading":
return "Memuat data";
case "success":
return `Ada ${state.data.length} data`;
case "error":
return state.message;
}
}Keuntungannya, data hanya tersedia pada status yang tepat. Ini mengurangi risiko membaca data saat request masih loading.
Generics
Generics dipakai saat function atau tipe perlu bekerja untuk banyak tipe tanpa kehilangan informasi tipe.
function firstItem<T>(items: T[]): T | undefined {
return items[0];
}
const firstNumber = firstItem([10, 20, 30]);
const firstName = firstItem(["Ayu", "Bima"]);T adalah placeholder tipe. TypeScript akan mengisinya berdasarkan argumen yang diberikan.
Contoh yang lebih dekat dengan API:
type PaginatedResult<T> = {
items: T[];
page: number;
totalPages: number;
};
type Tutorial = {
id: string;
title: string;
};
const result: PaginatedResult<Tutorial> = {
items: [{ id: "t1", title: "TypeScript" }],
page: 1,
totalPages: 3,
};Dengan generics, struktur pagination bisa dipakai ulang untuk tutorial, course, comment, atau data lain.
Utility Types
TypeScript menyediakan utility types untuk membuat tipe turunan.
type User = {
id: string;
name: string;
email: string;
passwordHash: string;
};
type PublicUser = Omit<User, "passwordHash">;
type UserPreview = Pick<User, "id" | "name">;
type UpdateUserInput = Partial<Pick<User, "name" | "email">>;
type ImmutableUser = Readonly<User>;Gunakan utility types untuk menghindari duplikasi tipe. Namun jangan terlalu memaksa. Jika tipe turunan menjadi sulit dibaca, tipe eksplisit kadang lebih baik.
Mendesain Tipe untuk API
Misalkan API mengembalikan daftar tutorial.
{
"items": [
{ "id": "t1", "title": "React Hooks", "published": true }
],
"page": 1,
"totalPages": 5
}Tipe TypeScript-nya bisa ditulis seperti ini.
type TutorialListItem = {
id: string;
title: string;
published: boolean;
};
type ListResponse<T> = {
items: T[];
page: number;
totalPages: number;
};
type TutorialListResponse = ListResponse<TutorialListItem>;Saat data berasal dari luar aplikasi, TypeScript hanya membantu saat compile time. Untuk validasi runtime, gunakan pemeriksaan manual atau library schema validation. Jangan menganggap semua JSON dari API pasti sesuai tipe hanya karena sudah diberi anotasi.
Kesalahan Umum
| Kesalahan | Mengapa bermasalah | Alternatif |
|---|---|---|
Terlalu sering memakai any | TypeScript kehilangan manfaat utamanya | Gunakan unknown, tipe eksplisit, atau generics. |
| Menulis tipe terlalu besar | Kode sulit dirawat | Pecah menjadi tipe kecil yang punya tujuan jelas. |
| Menganggap tipe sama dengan validasi runtime | Data eksternal tetap bisa salah | Validasi JSON sebelum dipakai. |
| Membuat union string tanpa narrowing | Cabang logika rawan salah | Gunakan discriminated union. |
Coba Sendiri
Buat tipe untuk fitur daftar course dengan ketentuan:
CourseLevelhanya boleh"pemula","menengah", atau"lanjutan".CourseCardmemilikiid,title,level,lessonCount, danpublishedAt.- Response API berbentuk generic
ListResponse<T>. - State UI memakai
LoadState<T>dengan statusidle,loading,success, danerror.
Setelah tipe selesai, buat function renderCourseCount yang menerima LoadState<CourseCard[]> dan mengembalikan pesan sesuai status.
type CourseLevel = "pemula" | "menengah" | "lanjutan";
type CourseCard = {
id: string;
title: string;
level: CourseLevel;
lessonCount: number;
publishedAt: string;
};
type LoadState<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; message: string };
function renderCourseCount(state: LoadState<CourseCard[]>): string {
if (state.status === "success") {
return `${state.data.length} course tersedia`;
}
if (state.status === "error") return state.message;
if (state.status === "loading") return "Memuat course";
return "Belum ada request";
}Cek Pemahaman
Kuis
Manakah pernyataan paling tepat tentang TypeScript?
Kuis
Mengapa discriminated union cocok untuk state loading data?
Ringkasan
TypeScript membantu kita menulis kontrak data yang jelas. Mulailah dari tipe primitif, object, union, dan narrowing. Setelah itu, gunakan generics dan utility types untuk membuat tipe yang fleksibel tetapi tetap mudah dibaca. Saat bekerja dengan data dari luar aplikasi, ingat bahwa TypeScript bukan validasi runtime; tetap lakukan validasi sebelum data dipercaya.