Menu

Record di TypeScript: Penjelasan Record<K, V> dan Contohnya

Record<K, V> adalah object type yang key-nya bertipe K dan semua nilainya bertipe V. Pelajari Record dengan key string dan key union (setiap key wajib ada), Partial<Record>, perulangan atas Record dengan key bertipe, jebakan key yang tidak ada, dan kapan sebaiknya memakai index signature atau Map.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

Record<K, V> adalah utility type bawaan untuk object yang key-nya bertipe K dan semua nilainya bertipe V. Dengan key string, tipe ini menggambarkan dictionary; dengan union dari literal key, tipe ini menggambarkan object yang harus punya tepat key-key itu.

Record hanya ada di sistem tipe. Saat runtime, kedua object itu adalah object JavaScript biasa, jadi keduanya bekerja dengan object literal, spread, JSON.stringify, dan apa pun yang menerima object.

Sintaks dan Definisi

Record<Keys, Value>

Keys harus sesuatu yang bisa menjadi key object: string, number, symbol, union dari literal string atau number, atau template literal type. Value boleh tipe apa pun. Seluruh definisinya di standard library TypeScript hanya satu baris, sebuah mapped type:

type Record<K extends keyof any, T> = {
  [P in K]: T;
};

keyof any adalah string | number | symbol, himpunan semua tipe key yang mungkin. [P in K]: T membuat satu property bertipe T untuk setiap anggota K. Itulah yang menjelaskan kedua perilaku di bawah: K yang luas seperti string menghasilkan index signature (key apa pun), dan K berupa union menghasilkan satu property wajib untuk setiap anggotanya.

Key Union: Setiap Key Wajib Ada

Kalau key-nya berupa union dari literal, sebuah Record harus mencantumkan setiap key itu, dan tidak boleh yang lain. Ini mengubah compiler menjadi daftar periksa:

Tambahkan "cancelled" ke Status dan kedua object itu berhenti lolos kompilasi sampai kamu memberi status baru itu label dan warna. Melewatkan sebuah key, atau menambahkan key yang tidak ada di union, adalah error kompilasi:

index.ts(4,7): error TS2741: Property 'error' is missing in type '{ idle: string; loading: string; success: string; }' but required in type 'Record<Status, string>'.
index.ts(11,54): error TS2353: Object literal may only specify known properties, and 'paused' does not exist in type 'Record<Status, string>'.

Hal yang sama berlaku dengan string enum sebagai tipe key: Record<Color, string> mewajibkan satu entri untuk setiap anggota enum.

Record<string, T> dan Key yang Tidak Ada

Dengan key string, key apa pun diperbolehkan, dan TypeScript memberi setiap pembacaan tipe T, bahkan untuk key yang tidak ada. Saat runtime, key yang tidak ada menghasilkan undefined:

Ini adalah bug Record yang paling umum. Ada tiga cara menanganinya: cek dengan in atau Object.hasOwn sebelum membaca, deklarasikan nilainya sebagai V | undefined, atau aktifkan opsi compiler noUncheckedIndexedAccess, yang menambahkan | undefined ke setiap pembacaan index signature di seluruh proyek. Record dengan key union tidak punya masalah ini, karena setiap key dijamin ada.

Partial<Record<K, V>>: Hanya Sebagian Key

Untuk memakai union key tanpa mewajibkan semuanya, bungkus Record dengan Partial. Pembacaan lalu mengembalikan V | undefined, dan itu memang jujur:

Key yang salah eja seperti jp tetap menjadi error, dan itulah keunggulannya dibanding Record<string, string>.

Perulangan atas Record

Object.keys, Object.values dan Object.entries semuanya berfungsi. Masalahnya ada di tipe key: Object.keys mengembalikan string[] dan Object.entries mengembalikan [string, V][], tidak pernah union key milikmu:

TypeScript sengaja mempertahankan key sebagai string: sebuah object bisa punya property lebih banyak saat runtime daripada yang tercantum di tipenya, jadi menjanjikan Plan[] secara umum tidak aman. Untuk object yang kamu buat dari literal, seperti seats, cast-nya aman.

Membangun Record dari Data

Record adalah hasil yang biasa dari mengelompokkan atau mengindeks sebuah array. Mulai dari object kosong bertipe Record, lalu isi:

Book["genre"] memakai ulang union dari interface sebagai tipe key, jadi menambahkan genre ke Book membuat byGenre menuntut entri baru.

Record<string, unknown> dan Interface

Record<string, unknown> adalah tipe yang umum untuk "suatu object dengan key string". Tipe ini menerima object literal dan nilai yang bertipe type alias, tetapi interface ditolak:

index.ts(11,11): error TS2345: Argument of type 'User' is not assignable to parameter of type 'Record<string, unknown>'.
  Index signature for type 'string' is missing in type 'User'.

Interface bisa diperluas lewat declaration merging, jadi TypeScript tidak menganggapnya cocok dengan index signature; type alias tidak bisa dibuka ulang, jadi type User = { name: string } akan lolos. Perbaikan yang biasa adalah menerima object sebagai gantinya (kamu tetap bisa memanggil Object.keys padanya), membuat fungsinya generic (<T extends object>(obj: T)), atau mendeklarasikan User dengan type (halaman interface vs type membahas perbedaan lainnya).

Record vs Index Signature vs Map

Record<K, V>{ [key: string]: V }Map<K, V>
Ada saat runtimetidak, object biasatidak, object biasaya, sebuah class
Himpunan key yang tetapya, dengan K berupa uniontidaktidak
Tipe keystring, number, symbol, union literal, pola templatestring, number, symbol, pola templateapa pun, termasuk object
Tipe saat membaca key yang tidak adaV (dengan key string)VV | undefined dari get
Dicampur dengan property bernamalewat intersection &ya, dalam tipe yang samatidak
JSON dan spreadyayatidak, konversi dulu
UkuranObject.keys(r).lengthObject.keys(o).lengthm.size
Sering menambah dan menghapusbisabisamemang dirancang untuk itu

Pilih Record dengan key union setiap kali himpunan key-nya diketahui: hanya opsi ini yang mengecek bahwa setiap key ada. Untuk key string yang terbuka, Record<string, V> dan index signature bisa saling menggantikan, dan banyak codebase memilih Record karena lebih mudah dibaca. Pakai Map kalau key ditambah dan dihapus saat runtime, kalau key-nya bukan string, atau kalau kamu butuh ukuran dan urutan penyisipan tanpa kerja tambahan.

Pertanyaan yang sering diajukan

Apa itu Record di TypeScript?

Record<K, V> adalah utility type bawaan untuk object yang key-nya bertipe K dan semua nilainya bertipe V. Record<string, number> adalah object dengan key string apa pun dan nilai number; Record<"en" | "de", string> adalah object dengan tepat key en dan de, keduanya string.

Apa perbedaan Record dan Map di TypeScript?

Record adalah tipe untuk object JavaScript biasa, jadi ia bekerja dengan object literal, JSON, dan spread, lalu menghilang saat kompilasi. Map adalah class runtime dengan get, set, has dan size, menjaga urutan penyisipan untuk semua key, menerima tipe key apa pun (termasuk object), dan get mengembalikan V | undefined. Pakai Record untuk data yang tetap atau berbentuk JSON, dan Map untuk key yang ditambah dan dihapus saat runtime.

Apa perbedaan Record<string, T> dan { [key: string]: T }?

Untuk nilainya, keduanya tipe yang sama: Record<string, T> dijabarkan menjadi object type dengan index signature string. Ada dua perbedaan kecil: index signature bisa diberi nama dan ditaruh di samping property lain dalam tipe yang sama, dan keyof Record<string, T> adalah string, sedangkan keyof { [key: string]: T } adalah string | number.

Bagaimana cara melakukan perulangan atas Record di TypeScript?

Pakai Object.entries(record) untuk pasangan key-nilai, Object.keys untuk key, dan Object.values untuk nilai. Key-nya dikembalikan sebagai string, bukan sebagai K, karena sebuah object bisa menyimpan key tambahan saat runtime. Kalau Record-nya punya union key yang diketahui, lakukan cast: (Object.keys(r) as Array<keyof typeof r>).

Bagaimana cara membuat hanya sebagian key Record yang wajib?

Dengan key union, Record<K, V> mewajibkan setiap key. Bungkus dengan Partial untuk membuat semuanya opsional: Partial<Record<Lang, string>>. Untuk campuran, pakai intersection: Record<"en", string> & Partial<Record<"de" | "fr", string>> mewajibkan en dan mengizinkan yang lain.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai