Tiga mekanisme ini dipakai untuk memperluas atau menyusun ulang tipe tanpa menyentuh kode aslinya:
Declaration Merging: TypeScript menyatukan beberapa deklarasi dengan nama sama dalam satu scope menjadi satu deklarasi final.
Module Augmentation: menambah deklarasi (interface, class, fungsi, dll.) ke dalam module yang sudah ada lewat declare module.
Decorators: ekspresi @nama yang menempel di class, method, property, accessor, atau parameter, lalu dieksekusi saat deklarasi tersebut dievaluasi — dipakai untuk menyuntikkan metadata atau memodifikasi perilaku.
Ketiganya beririsan di satu tema: TypeScript fleksibel karena tipenya bisa digabung dan diperluas secara deklaratif, bukan cuma ditulis ulang.
Kapan dipakai
Declaration merging dipakai saat kamu punya beberapa potongan tipe dengan nama sama dan ingin TypeScript menggabungkannya otomatis (misal interface yang didefinisikan di dua file berbeda, atau memperluas definisi bawaan).
Module augmentation dipakai saat kamu ingin menambah properti/method ke tipe milik package pihak ketiga atau ke tipe bawaan seperti Window, tanpa fork kodenya.
Decorators dipakai saat kamu ingin menandai class/member dengan metadata (ORM, validasi, routing, DI) atau memodifikasi descriptor secara terpusat.
Cara kerja
Declaration merging
Yang bisa di-merge: interface, namespace, enum, dan kombinasi class + namespace. Aturan inti: deklarasi dengan nama sama digabung; kalau ada member dengan key sama, tipenya harus kompatibel (interface di-merge, bukan ditimpa).
ts
Selesai membaca dokumentasi ini?
Kembali ke rujukan utama TypeScript untuk melanjutkan topik lainnya.
Function overloads tidak di-merge lewat interface, tetapi lewat deklarasi overload berturut-turut:
ts
function format(value: number): string;function format(value: Date): string;function format(value: number | Date): string { return value.toString();}
Class bisa di-merge dengan namespace untuk menambah member statis, tapi tidak bisa di-merge dengan interface secara langsung:
ts
class Counter { count = 0;}namespace Counter { export const reset = () => 0; // menempel sebagai Counter.reset}
Module augmentation
Bentuk paling umum: memperluas tipe di dalam sebuah module. Syarat: file augmentation harus punya setidaknya satu import/export agar dianggap sebagai module, bukan script global.
ts
// menambah field ke tipe milik package pihak ketigaimport 'some-lib';declare module 'some-lib' { interface ClientOptions { retryCount?: number; }}
Untuk tipe global bawaan (seperti Window, Array, process), pakai declare global:
ts
declare global { interface Window { analyticsId: string; }}export {}; // agar file ini dianggap module
Decorators
Decorator adalah function yang dipanggil saat deklarasi dievaluasi. Ada dua mode di TypeScript:
Ini sumber kebingungan paling sering. Perbedaannya bukan di keyword, tapi di konteks file dan tujuannya.
Loading diagram...
Ringkasnya:
declare module 'x' { ... }di dalam file module (ada import/export) → augmentation: memperluas module x yang sudah punya tipe.
declare module 'x' { ... }di dalam file global (tanpa import/export) → ambient module declaration: memberi tipe pada module yang sama sekali belum punya definisi .d.ts.
declare global { ... } → memperluas tipe global bawaan (Window, Array, dll.).
Jadi declare module dipakai untuk dua hal berbeda tergantung ada/tidaknya import/export di file. Augmentation selalu butuh file module.
Catatan implementasi
File augmentation wajib punya import/export, kalau tidak TypeScript menganggapnya script global dan malah membuat ambient declaration baru, bukan memperluas yang ada.
Untuk augment module dengan export = (CommonJS), pakai declare module 'x' { import x = require('x'); ... } sesuai bentuk modulenya.
Decorator legacy butuh experimentalDecorators: true di tsconfig. Tanpa itu, @nama akan error.
Metadata butuh emitDecoratorMetadata: truedanreflect-metadata di-import sekali di entry point.
Urutan evaluasi decorator: dari bawah ke atas untuk property/parameter dalam satu class, dan outer-to-inner untuk decorator bertingkat (@a @b → b dieksekusi duluan).
Kesalahan umum saat override tipe bawaan
Menimpa signature yang sudah ada.declare global { interface Array<T> { push(x: T): void } } untuk mengubah push akan error subsequent property declarations must have the same type. Aturan lib bawaan: hanya boleh menambah member, tidak mengganti atau menghapus.
Salah pakai declare module vs declare global. Memperluas Window dengan declare module 'window' tidak akan berlaku; pakai declare global { interface Window {...} }.
Augmentasi global bentrok antar file. Dua declare global { interface Array<T> {...} } dengan member key sama tapi tipe beda akan memunculkan Duplicate identifier atau error merging.
Lupa export {}. File yang cuma berisi declare global tanpa export dianggap script global dan bisa mendeklarasikan ulang secara tidak sengaja. Tambahkan export {}.
Augmentasi package salah path.declare module 'react' memperluas React asli; kalau nama module salah ketik, TypeScript membuat module kosong baru alih-alih merge, sehingga tipe asli "hilang" di mata augmentation tersebut.
Mixin lewat class merging yang tidak valid.class A {} tidak bisa di-merge dengan interface A {}. Gunakan namespace untuk menempelkan member statis ke class.
Metadata kosong. Lupa emitDecoratorMetadata atau belum import 'reflect-metadata' → Reflect.getMetadata selalu undefined.
Mencampur decorator legacy dan TC39 tanpa memilih satu mode di tsconfig, menghasilkan signature decorator yang tidak cocok.
Loading diagram...
Referensi terkait
Types, Interfaces, Generics, Narrowing, dan Utility Types di TypeScript