Konstanta #
Konstanta adalah nilai yang tidak berubah selama masa hidup aplikasi — batas percobaan login, URL API, kode warna tema, nama environment, dan sejenisnya. Di JavaScript, kita sudah terbiasa menggunakan const untuk ini. Tapi TypeScript membuka dimensi baru yang lebih kaya: selain melindungi binding variabel seperti yang dilakukan const di JavaScript, TypeScript bisa menjamin bahwa isi sebuah nilai benar-benar tidak bisa berubah melalui Readonly<T>, Object.freeze, dan as const. Memahami perbedaan antara “binding tidak bisa diubah” dan “nilai tidak bisa diubah” adalah kunci untuk menulis konstanta yang benar-benar konstanta.
const — Perlindungan Binding, Bukan Nilai
#
const adalah kata kunci paling umum untuk mendeklarasikan konstanta. Ia memastikan bahwa sebuah variabel tidak bisa di-reassign ke nilai lain setelah deklarasi awal. Tapi penting dipahami: const hanya melindungi binding (hubungan antara nama variabel dan nilai yang dirujuknya), bukan isi dari nilai tersebut.
// Primitif dengan const — benar-benar tidak bisa berubah
const PI: number = 3.14159265358979;
const NAMA_PERUSAHAAN: string = "MuslimApps";
const MAKS_PERCOBAAN: number = 3;
const FITUR_AKTIF: boolean = true;
// Tidak bisa di-reassign
// PI = 3.14; // ✗ Error: Cannot assign to 'PI' because it is a constant
// NAMA_PERUSAHAAN = ""; // ✗ Error: Cannot assign to 'NAMA_PERUSAHAAN' because it is a constant
Untuk nilai primitif (number, string, boolean), const sudah cukup memberikan jaminan penuh — nilai tidak bisa berubah sama sekali. Masalah muncul saat konstanta berupa object atau array.
const dengan Object — Mitos Immutability
#
Ini adalah salah satu kesalahpahaman paling umum tentang const:
const konfigurasi = {
host: "localhost",
port: 5432,
ssl: false,
};
// ANTI-PATTERN: Mengira const object tidak bisa dimodifikasi
// Faktanya, properti object BISA diubah meski variabelnya const
konfigurasi.port = 9999; // ✓ Tidak ada error — ini DIIZINKAN TypeScript
konfigurasi.ssl = true; // ✓ Tidak ada error — ini DIIZINKAN TypeScript
// Yang tidak bisa dilakukan adalah reassign ke object baru
// konfigurasi = { host: "prod.db.com", port: 5432, ssl: true }; // ✗ Error
Artinya, kode seperti di bawah ini sepenuhnya legal meskipun variabelnya const:
const pengguna = { nama: "Budi", peran: "user" };
function tingkatkanPeran(p: typeof pengguna) {
p.peran = "admin"; // Modifikasi object yang diterima sebagai argumen — bisa!
}
tingkatkanPeran(pengguna);
console.log(pengguna.peran); // "admin" — object sudah berubah!
Readonly<T> — Immutability di Level Tipe
#
Readonly<T> adalah utility type bawaan TypeScript yang membuat semua properti sebuah tipe menjadi readonly — compiler akan menolak setiap upaya modifikasi properti tersebut.
// Tanpa Readonly — properti bisa dimodifikasi
const konfigDB = {
host: "localhost",
port: 5432,
};
konfigDB.port = 9999; // ✓ Diizinkan — tidak aman untuk konfigurasi
// Dengan Readonly — compiler menolak modifikasi properti
const konfigDBSafe: Readonly<{
host: string;
port: number;
}> = {
host: "localhost",
port: 5432,
};
// konfigDBSafe.port = 9999; // ✗ Error: Cannot assign to 'port' because it is a read-only property
Readonly<T> juga bisa diterapkan pada interface:
interface KonfigurasiAplikasi {
readonly namaAplikasi: string;
readonly versi: string;
readonly urlApi: string;
readonly batasRequest: number;
}
const KONFIGURASI: KonfigurasiAplikasi = {
namaAplikasi: "MuslimApps",
versi: "2.1.0",
urlApi: "https://api.muslimapps.id",
batasRequest: 100,
};
// KONFIGURASI.urlApi = "https://hack.com"; // ✗ Error — dilindungi readonly
ReadonlyArray<T> — Array yang Tidak Bisa Dimodifikasi
#
Versi Readonly untuk array mencegah operasi yang memodifikasi array seperti push, pop, splice, dan sejenisnya:
const DAFTAR_BAHASA: ReadonlyArray<string> = ["id", "en", "ar"];
// Atau dengan sintaks alternatif:
const DAFTAR_ZONA: readonly string[] = ["Asia/Jakarta", "Asia/Makassar", "Asia/Jayapura"];
// DAFTAR_BAHASA.push("fr"); // ✗ Error: Property 'push' does not exist on type 'readonly string[]'
// DAFTAR_BAHASA[0] = "ms"; // ✗ Error: Index signature in type 'readonly string[]' only permits reading
// Boleh dibaca
console.log(DAFTAR_BAHASA[0]); // "id"
console.log(DAFTAR_BAHASA.length); // 3
Readonly<T>hanya bersifat shallow — ia melindungi properti di level pertama, tapi tidak melindungi properti dari object bersarang. Jika kamu butuh deep immutability di level TypeScript, kamu perlu membuat tipeDeepReadonlysendiri atau menggunakan library sepertits-essentials.
Object.freeze — Immutability di Level Runtime
#
Readonly<T> hanya memberikan perlindungan di level TypeScript — setelah dikompilasi ke JavaScript, tidak ada yang mencegah kode JS memodifikasi object tersebut. Object.freeze() memberikan perlindungan yang nyata di level runtime: object yang di-freeze tidak bisa dimodifikasi sama sekali, bahkan di JavaScript biasa.
const BATAS_SISTEM = Object.freeze({
maxPenggunaPerHari: 10000,
maxFileUpload: 10, // dalam MB
maxDurasiSesi: 3600, // dalam detik
maxPercobaan: 3,
});
// Di level TypeScript — TypeScript menyimpulkan semua properti sebagai readonly
// BATAS_SISTEM.maxPercobaan = 5; // ✗ Error kompilasi
// Di level runtime — Object.freeze mencegah modifikasi di JavaScript
// Bahkan kode JS yang tidak lewat TypeScript tidak bisa mengubahnya
Kombinasi Object.freeze dengan tipe eksplisit adalah pendekatan paling defensif:
interface KonfigPembayaran {
readonly providerAktif: string[];
readonly batasTransaksiHarian: number;
readonly matauang: string;
}
const KONFIG_PEMBAYARAN: Readonly<KonfigPembayaran> = Object.freeze({
providerAktif: ["midtrans", "xendit", "gopay"],
batasTransaksiHarian: 50_000_000,
matauang: "IDR",
});
Perbandingan Pendekatan Immutability #
| Pendekatan | Perlindungan Kompilasi | Perlindungan Runtime | Deep? |
|---|---|---|---|
const saja | Hanya binding | Tidak ada | — |
Readonly<T> | Semua properti | Tidak ada | Shallow |
Object.freeze() | Tidak ada | Ya (shallow) | Shallow |
Readonly<T> + freeze | Semua properti | Ya (shallow) | Shallow |
as const — Const Assertion
#
as const adalah fitur TypeScript yang sangat powerful untuk konstanta. Ia memberitahu compiler untuk memperlakukan nilai seakurat mungkin — mengubah tipe menjadi literal type yang paling spesifik, dan membuat semua properti menjadi readonly sekaligus.
// Tanpa as const — TypeScript menyimpulkan tipe yang lebar
const titikTanpa = { x: 10, y: 20 };
// Tipe: { x: number; y: number } — bisa diisi angka apapun
// Dengan as const — TypeScript menyimpulkan tipe literal yang paling spesifik
const titikDengan = { x: 10, y: 20 } as const;
// Tipe: { readonly x: 10; readonly y: 20 } — nilai hanya bisa 10 dan 20
// titikDengan.x = 99; // ✗ Error: Cannot assign to 'x' because it is a read-only property
as const sangat berguna untuk mendefinisikan konstanta yang nilainya akan digunakan sebagai tipe literal:
// Tanpa as const — tipe direction adalah string, terlalu lebar
const ARAH_TANPA = {
ATAS: "atas",
BAWAH: "bawah",
KIRI: "kiri",
KANAN: "kanan",
} ;
type ArahTanpa = typeof ARAH_TANPA[keyof typeof ARAH_TANPA]; // string
// Dengan as const — tipe adalah union literal yang presisi
const ARAH = {
ATAS: "atas",
BAWAH: "bawah",
KIRI: "kiri",
KANAN: "kanan",
} as const;
type Arah = typeof ARAH[keyof typeof ARAH];
// Tipe: "atas" | "bawah" | "kiri" | "kanan"
function pindah(arah: Arah): void {
console.log(`Bergerak ke ${arah}`);
}
pindah(ARAH.ATAS); // ✓ "atas"
pindah("kiri"); // ✓ literal string yang valid
// pindah("diagonal"); // ✗ Error: Argument of type '"diagonal"' is not assignable
as const untuk Array
#
// Tanpa as const — tipe adalah string[]
const METODE_PEMBAYARAN_TANPA = ["transfer", "kartu_kredit", "dompet_digital"];
// Tipe: string[]
// Dengan as const — tipe adalah tuple literal readonly
const METODE_PEMBAYARAN = ["transfer", "kartu_kredit", "dompet_digital"] as const;
// Tipe: readonly ["transfer", "kartu_kredit", "dompet_digital"]
type MetodePembayaran = typeof METODE_PEMBAYARAN[number];
// Tipe: "transfer" | "kartu_kredit" | "dompet_digital"
Literal Types sebagai Konstanta #
Literal types memungkinkan kamu mendefinisikan variabel yang tipenya adalah nilai spesifik itu sendiri — bukan kategori umum seperti string atau number, tapi nilai yang tepat:
// Literal type — variabel hanya bisa bernilai persis nilai yang ditentukan
const STATUS_AKTIF: "aktif" = "aktif";
const VERSI: 2 = 2;
const DEBUG_MODE: false = false;
// STATUS_AKTIF = "nonaktif"; // ✗ Error: Type '"nonaktif"' is not assignable to type '"aktif"'
Literal types lebih berguna saat dikombinasikan dengan union untuk mendefinisikan set nilai yang valid:
// Union literal sebagai konstanta tipe
type Environment = "development" | "staging" | "production";
type LogLevel = "debug" | "info" | "warn" | "error";
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
// Fungsi dengan parameter yang dibatasi nilai literalnya
function setEnvironment(env: Environment): void {
console.log(`Environment diset ke: ${env}`);
}
setEnvironment("production"); // ✓
// setEnvironment("testing"); // ✗ Error: Argument of type '"testing"' is not assignable to parameter of type 'Environment'
Enum sebagai Kelompok Konstanta #
Enum adalah cara TypeScript untuk mengelompokkan konstanta yang berkaitan ke dalam satu unit yang bernama. Enum menghasilkan kode JavaScript nyata saat dikompilasi (berbeda dengan type atau interface).
// Numeric enum — nilai numerik otomatis
enum PrioritasTiket {
Rendah = 1,
Sedang = 2,
Tinggi = 3,
Kritis = 4,
}
// String enum — lebih deskriptif dan mudah di-debug
enum StatusTransaksi {
Menunggu = "MENUNGGU",
Diproses = "DIPROSES",
Berhasil = "BERHASIL",
Gagal = "GAGAL",
Dibatalkan = "DIBATALKAN",
}
function prosesTransaksi(status: StatusTransaksi): string {
switch (status) {
case StatusTransaksi.Berhasil:
return "Transaksi selesai, terima kasih!";
case StatusTransaksi.Gagal:
return "Transaksi gagal, silakan coba lagi";
case StatusTransaksi.Dibatalkan:
return "Transaksi dibatalkan";
default:
return `Status: ${status}`;
}
}
const enum — Enum yang Diinline Compiler
#
const enum adalah varian enum yang lebih efisien — compiler mengganti setiap penggunaan enum dengan nilai literalnya secara langsung, sehingga tidak ada object enum di JavaScript output:
// const enum — tidak menghasilkan object JavaScript
const enum KodeHTTP {
OK = 200,
Created = 201,
BadRequest = 400,
Unauthorized = 401,
NotFound = 404,
InternalError = 500,
}
function tanganiResponse(kode: KodeHTTP): string {
if (kode === KodeHTTP.OK) return "Sukses";
if (kode === KodeHTTP.NotFound) return "Tidak ditemukan";
return "Error tidak diketahui";
}
// Setelah dikompilasi, kode di atas menjadi:
// if (kode === 200) return "Sukses";
// if (kode === 404) return "Tidak ditemukan";
// Nilai enum langsung disubstitusi — lebih efisien di runtime
Kapan Enum vs Union Literal #
Gunakan Enum jika:
✓ Butuh iterasi (Object.values(MyEnum))
✓ Butuh reverse mapping (nilai → nama)
✓ Membuat konstanta numerik yang bermakna
✓ Kode di-share dengan proyek non-TypeScript
Gunakan Union Literal jika:
✓ Tidak butuh iterasi atau reverse mapping
✓ Ingin tipe yang lebih ringan (tidak ada JS output)
✓ Mudah dikombinasikan dengan tipe lain
✓ Bekerja dalam file yang sama atau satu modul kecil
Organisasi Konstanta dalam Modul #
Proyek nyata biasanya punya banyak konstanta yang perlu diorganisir dengan baik. Pendekatan yang disarankan adalah mengelompokkannya dalam modul tersendiri berdasarkan domain:
// src/constants/api.ts
export const API = {
BASE_URL: "https://api.muslimapps.id",
VERSION: "v2",
TIMEOUT: 30_000, // 30 detik dalam milidetik
ENDPOINTS: {
AUTH: "/auth",
PROFIL: "/profil",
JADWAL_SHOLAT: "/jadwal-sholat",
KIBLAT: "/kiblat",
},
} as const;
export type ApiEndpoint = typeof API.ENDPOINTS[keyof typeof API.ENDPOINTS];
// src/constants/ui.ts
export const UI = {
ANIMASI_DURASI: 300, // ms
DEBOUNCE_DELAY: 500, // ms
MAKS_KARAKTER_BIO: 160,
UKURAN_HALAMAN_DEFAULT: 20,
} as const;
export const WARNA = {
HIJAU_PRIMER: "#1a7f5a",
EMAS: "#c9a84c",
GELAP: "#1a1a2e",
TERANG: "#f8f9fa",
} as const;
// src/constants/validasi.ts
export const VALIDASI = {
PANJANG_PASSWORD_MIN: 8,
PANJANG_NAMA_MIN: 2,
PANJANG_NAMA_MAX: 100,
MAKS_UKURAN_FOTO: 5 * 1024 * 1024, // 5 MB dalam bytes
FORMAT_TELEPON: /^(\+62|0)[0-9]{9,12}$/,
} as const;
// src/constants/index.ts — re-export semua dari satu titik
export * from "./api";
export * from "./ui";
export * from "./validasi";
Dengan struktur ini, import di tempat lain menjadi bersih:
// Di komponen atau service lain
import { API, VALIDASI, WARNA } from "@/constants";
function ambilJadwalSholat(kota: string) {
return fetch(`${API.BASE_URL}${API.VERSION}${API.ENDPOINTS.JADWAL_SHOLAT}?kota=${kota}`, {
signal: AbortSignal.timeout(API.TIMEOUT),
});
}
Alur Memilih Pendekatan Konstanta #
flowchart TD
A{Apa jenis nilai\nkonstantamu?} --> B[Primitif\nnumber, string, boolean]
A --> C[Object / Array]
A --> D[Kelompok nilai\nyang berkaitan]
B --> B1[const dengan\nSCREAMING_SNAKE_CASE]
C --> E{Perlu benar-benar\nimmutable?}
E -- Tidak --> F[const biasa]
E -- Ya, kompilasi saja --> G[Readonly + as const]
E -- Ya, runtime juga --> H[Object.freeze\n+ Readonly]
D --> I{Perlu iterasi atau\nreverse mapping?}
I -- Ya --> J[Enum atau const enum]
I -- Tidak --> K[Union literal type\natau as const object]
style B1 fill:#51cf66,color:#fff
style F fill:#339af0,color:#fff
style G fill:#339af0,color:#fff
style H fill:#51cf66,color:#fff
style J fill:#fcc419,color:#000
style K fill:#51cf66,color:#fffRingkasan #
consthanya melindungi binding — ia mencegah reassign, tapi properti object dan elemen array masih bisa diubah; jangan salah kaprah mengiraconst= immutable.Readonly<T>untuk immutability di level tipe — semua properti menjadi readonly dan compiler menolak modifikasi; tapi perlindungan ini hanya ada saat kompilasi, tidak di runtime.Object.freeze()untuk immutability di runtime — memberikan perlindungan nyata di JavaScript; kombinasikan denganReadonly<T>untuk proteksi ganda di kompilasi dan runtime.as constadalah alat terkuat untuk konstanta — ia menyempitkan tipe ke literal paling spesifik, membuat semua propertireadonly, dan mengaktifkan ekstraksi tipe union dari nilai konstanta.- Enum cocok untuk kelompok konstanta yang perlu diiterasi atau punya nilai numerik bermakna; untuk nilai string yang sederhana, union literal atau
as constobject lebih ringan dan fleksibel.const enumlebih efisien dari enum biasa — nilai di-inline langsung oleh compiler, tidak menghasilkan object JavaScript di output, sehingga lebih kecil ukuran bundlenya.- Organisasi modul konstanta — kelompokkan konstanta berdasarkan domain (
api.ts,ui.ts,validasi.ts) dan re-export dariindex.ts; ini memudahkan import dan mencegah “magic number” tersebar di seluruh codebase.- Gunakan
SCREAMING_SNAKE_CASEuntuk konstanta global yang benar-benar tidak berubah; ini memberi sinyal visual yang jelas bahwa nilai ini tidak seharusnya disentuh.