Kontrak Konten Bertipe yang Nyelamatin Refactor
Bentuk kartu perusahaan berubah tiga kali dalam seminggu, tapi kontrak konten bertipe tetap: field opsional plus guard bikin data kosong otomatis singkir.
Ringkasan
Penulis bikin kartu tujuh perusahaan di situs klien pakai Next.js, datanya diisi dari file TS lokal. Dia bikin kontrak konten pakai interface TypeScript, field opsional plus guard biar data kosong atau belum terverifikasi nggak pernah nampil di layar. Hasilnya nambah kartu cuma perlu edit file konten, tanpa sentuh komponen sama sekali.
Waktu itu saya lagi ngurusin bagian About di situs company profile klien yang pakai Next.js App Router. Situsnya bilingual, dan saya butuh nampilin kartu untuk tujuh perusahaan grup. Versi pertama saya bikin cepat: list kartu dirender dari array di file konten, langsung dimap di JSX komponen.
Setiap kartu pelan-pelan nambah field baru. Awalnya cuma nama dan deskripsi, terus muncul chip sektor, baris meta tahun berdiri, sampai lokasi. Field yang datanya belum saya verifikasi saya isi pakai string harfiah [PLACEHOLDER] di file konten. Tujuannya biar data yang belum kelar langsung kelihatan pas lagi review.
Waktu pertama kali ngisi array-nya, saya sempat salah asumsi. Saya pikir field tahun berdiri bakal selalu ada. Ternyata ada perusahaan grup yang emang nggak punya angka tahun yang bisa dipertanggungjawabkan. Baris meta yang isinya kosong itu nampilin kekosongan aneh di layar. Dari situ saya sadar, saya nggak boleh ngandelin asumsi soal isi data.
Bentuk berubah, aturan main tetap
Beberapa hari kemudian, revisi masuk. Baris meta tahun dan lokasi dibuang. Seluruh blok kartu dipindah ke section sendiri. Bentuk datanya berubah tiga kali dalam seminggu.
Tapi ada satu hal yang nggak berubah: kontrak konten yang saya bikin di awal tetap jalan. Komponen nggak pernah sekali pun nge-render field yang belum terverifikasi. Kenapa bisa? Karena kontraknya emang didesain bikin penyembunyian jadi default. Field opsional plus guard truthiness bikin data yang kosong atau nggak ada otomatis nggak tampil. Saya nggak butuh feature flag atau if-ladder yang njelimet. Interface TypeScript-nya yang melakukan sensor buat saya.
Mekanismenya gini. Di TypeScript, object types mendefinisikan bentuk data. Tanda tanya di belakang nama property nandain field itu opsional, dan opsional di sini artinya kalo property itu diisi, tipenya harus sesuai aturan [6].
Hal yang saya suka banget dari TypeScript adalah excess property checking. Kalo saya salah ketik nama field pas nulis object literal, compiler langsung ngelempar error. Nggak ada cerita data salah ketik masuk produksi secara diam-diam [6]. Bayangin kalo saya ngetik webiste padahal maksudnya website. Tanpa gate ini, komponen bakal diem aja dan link-nya hilang. Dengan gate ini, build gagal dan saya dipaksa benerin sebelum deploy.
Di sisi React, komponen nerima satu object props. Props sifatnya read-only snapshot: tiap render, komponen dapet versi baru [7]. Aliran datanya jadi gampang diprediksi; kalo isi file konten berubah, output ikut berubah tanpa ada mutasi aneh di tengah jalan.
interface CompanyCard {
name: string;
description: string;
website?: string;
}
function Card({ data }: { data: CompanyCard }) {
return (
<div>
<h3>{data.name}</h3>
<p>{data.description}</p>
{data.website != null && data.website !== "" && (
<a href={data.website}>Kunjungi situs</a>
)}
</div>
);
}
Guard sederhana, efek besar
Coba perhatiin baris conditional render di atas. Di JavaScript, statement if memaksa kondisinya jadi boolean. String kosong, null, dan undefined semuanya ter-coerce jadi false [8].
Saya pakai pengecekan == null karena ini nyakup null dan undefined sekaligus dalam satu langkah [8]. Ditambah cek string kosong, komponen dijamin nggak bakal nge-render link kosong atau teks placeholder ke layar user. Kalo datanya belum ada, ya nggak usah ditampilkan. Kejujurannya nggak perlu dijagain polisi tambahan; dia udah jadi bagian dari struktur data.
Dulu saya sempat mikir, mending pakai CMS headless aja biar konten bisa diubah tanpa nyentuh kode. Setup-nya emang keliatan keren di portfolio. Tapi setelah ngejalanin project ini, pendapat saya bulat: buat situs bilingual skala kecil dengan struktur halaman yang stabil, typed content module dari file TS lokal jauh lebih nguntungin. Nggak ada biaya server CMS, nggak ada webhook syncing, dan type safety-nya nempel langsung dari source code.
Nambah kartu tanpa deg-degan
Keuntungan terbesar pola ini kerasa pas ada penambahan entitas. Klien minta nambahin induk perusahaan sebagai kartu ketujuh.
Kalo pake pendekatan lama yang penuh asumsi, nambah kartu pasti bikin deg-degan; takut ada UI pecah karena field baru belum ke-handle. Dengan kontrak ini, nambah perusahaan ketujuh cuma butuh edit file konten. Benar-benar nol perubahan di komponen.
Contract-based content modeling kadang kerasa kaku di awal. Kita dipaksa mikirin bentuk data sebelum nulis UI. Tapi kekakuan di awal itu yang nyelamatin kita dari bug aneh pas project makin gede. Kita nggak lagi nulis kode yang nebak-nebak isi data, tapi nulis kode yang bereaksi terhadap kepastian.
## Sources [6] https://www.typescriptlang.org/docs/handbook/2/objects.html [7] https://react.dev/learn/passing-props-to-a-component [8] https://www.typescriptlang.org/docs/handbook/2/narrowing.html