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 tipe DeepReadonly sendiri atau menggunakan library seperti ts-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 #

PendekatanPerlindungan KompilasiPerlindungan RuntimeDeep?
const sajaHanya bindingTidak ada
Readonly<T>Semua propertiTidak adaShallow
Object.freeze()Tidak adaYa (shallow)Shallow
Readonly<T> + freezeSemua propertiYa (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:#fff

Ringkasan #

  • const hanya melindungi binding — ia mencegah reassign, tapi properti object dan elemen array masih bisa diubah; jangan salah kaprah mengira const = 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 dengan Readonly<T> untuk proteksi ganda di kompilasi dan runtime.
  • as const adalah alat terkuat untuk konstanta — ia menyempitkan tipe ke literal paling spesifik, membuat semua properti readonly, 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 const object lebih ringan dan fleksibel.
  • const enum lebih 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 dari index.ts; ini memudahkan import dan mencegah “magic number” tersebar di seluruh codebase.
  • Gunakan SCREAMING_SNAKE_CASE untuk konstanta global yang benar-benar tidak berubah; ini memberi sinyal visual yang jelas bahwa nilai ini tidak seharusnya disentuh.

← Sebelumnya: Variabel   Berikutnya: Tipe Data →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact