Menu

keyof di TypeScript: Key Object sebagai Tipe, dengan Contoh

keyof menerima tipe object dan memberimu union dari nama-nama propertinya. Pelajari keyof dengan interface, keyof typeof untuk object biasa, akses properti bertipe dengan generic, index signature (string | number), dan kenapa Object.keys mengembalikan string[].

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

keyof menerima tipe object dan menghasilkan union dari nama-nama propertinya. keyof User di bawah adalah tipe "id" | "name" | "email", jadi variabel bertipe itu hanya bisa berisi salah satu dari ketiga string tersebut.

keyof hanya ada di sistem tipe. Operator ini dihapus saat kode dikompilasi, jadi tidak pernah memeriksa apa pun saat runtime. Yang diberikannya adalah jaminan saat kompilasi bahwa sebuah key memang menunjuk ke properti yang ada: ganti nama email di interface, dan setiap "email" yang bertipe keyof User langsung menjadi error.

keyof typeof: Key dari Object Biasa

keyof bekerja pada tipe, bukan nilai. Untuk object yang kamu tulis sebagai nilai, ambil dulu tipenya dengan typeof, lalu ambil key-nya:

Baca dari dalam ke luar: typeof colors adalah { red: string; green: string; blue: string }, dan keyof dari tipe itu adalah union key-nya. Menulis keyof colors tanpa typeof gagal dengan error TS2749: 'colors' refers to a value, but is being used as a type here. Did you mean 'typeof colors'?.

Inilah cara yang umum untuk menurunkan union dari sebuah tabel lookup: tambahkan warna ke object itu, dan tipe Color ikut bertambah. typeof juga punya arti kedua saat runtime; halaman typeof membahas keduanya.

Akses Properti Bertipe dengan Generic

Pemakaian keyof yang paling umum adalah sebagai generic constraint. K extends keyof T menyatakan "K adalah salah satu key dari T", dan T[K] adalah tipe properti pada key tersebut:

Parameter tipe kedua itu penting. Dengan key: keyof T tanpa K, tipe kembaliannya adalah T[keyof T], yaitu union dari semua tipe properti (di sini string | number | boolean), jadi title.toUpperCase() akan gagal dengan error TS2339: Property 'toUpperCase' does not exist on type 'string | number | boolean'. Menangkap key yang persis di K mempertahankan tipe properti yang persis pula. T[K] adalah indexed access type, yang dijelaskan di halaman indexed access type.

Memberi Indeks dengan string Biasa

Error yang sangat sering muncul terjadi saat kamu memberi indeks pada object dengan string yang berasal dari tempat lain:

Compiler mencetak:

index.ts(10,10): error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Settings'.
  No index signature with a parameter of type 'string' was found on type 'Settings'.

String apa pun bisa sampai ke read, termasuk "colour", dan Settings tidak menyatakan apa pun tentang key itu. Ada dua cara memperbaikinya: hanya terima key yang benar-benar ada, atau periksa string yang tidak dikenal sebelum memakainya.

name is keyof Settings adalah type predicate: kalau fungsi itu mengembalikan true, TypeScript memperlakukan input sebagai key di dalam if. Halaman type guard menjelaskan predicate secara detail.

keyof dengan Index Signature: string | number

Untuk tipe dengan index signature, keyof mengembalikan tipe indeksnya, dan string index signature menghasilkan string | number, bukan hanya string:

JavaScript mengubah setiap key properti berupa angka menjadi string, jadi scores[42] dan scores["42"] adalah properti yang sama. TypeScript memodelkan hal itu dengan menyertakan number. Output-nya juga menunjukkan sebuah aturan JavaScript: key yang mirip bilangan bulat ditampilkan lebih dulu, berurutan naik, sebelum key string lainnya. Kalau fungsi generic hanya butuh key berupa string, tulis Extract<keyof T, string> atau string & keyof T; Exclude dan Extract punya halamannya sendiri.

Ringkasan Hasil keyof

TipeHasil keyof
{ id: number; name: string }"id" | "name"
{ [key: string]: number }string | number
{ [index: number]: string }number
{}never
anystring | number | symbol
{ a: 1; b: 2 } | { a: 3; c: 4 }"a" (key yang dimiliki setiap member)
{ a: 1 } & { c: 4 }"a" | "c" (key dari salah satunya)
string[]number ditambah setiap nama method array ("length", "push", ...)

Baris union dan intersection awalnya terlihat terbalik. Nilai bertipe union bisa berupa member yang mana saja, jadi hanya key yang ada di keduanya yang aman. Nilai bertipe intersection punya semua yang ada di kedua tipe, jadi ia punya semua key-nya.

Kenapa Object.keys Mengembalikan string[]

Object.keys(obj) diberi tipe string[], tidak pernah (keyof T)[]. Alasannya adalah structural typing: sebuah nilai bisa membawa lebih banyak properti daripada tipe yang dideklarasikan.

p bertipe Point, tetapi saat runtime ia punya key z. Kalau Object.keys menjanjikan ("x" | "y")[], kode yang mengandalkan janji itu akan salah untuk p. Cast tersebut aman untuk object literal yang kamu buat sendiri; untuk object yang datang dari luar, biarkan key-nya bertipe string dan periksa.

keyof di Mapped Type

keyof juga merupakan cara untuk menelusuri key sebuah tipe demi membangun tipe baru. Begitulah utility type bawaan Partial dan Readonly ditulis:

type MyPartial<T> = { [K in keyof T]?: T[K] };
type MyReadonly<T> = { readonly [K in keyof T]: T[K] };

interface User {
  id: number;
  name: string;
}

type UserDraft = MyPartial<User>; // { id?: number; name?: string }

[K in keyof T] mengunjungi setiap key, dan T[K] membaca tipe properti pada key tersebut. Mapped type punya halamannya sendiri, lengkap dengan modifier dan key remapping.

Pertanyaan yang sering diajukan

Apa fungsi keyof di TypeScript?

keyof T menghasilkan union dari nama-nama properti tipe T. Untuk interface User { id: number; name: string }, keyof User adalah "id" | "name". Operator ini hanya bekerja pada tipe, saat kompilasi, dan tidak menghasilkan kode JavaScript.

Apa itu keyof typeof di TypeScript?

keyof membutuhkan tipe, padahal object biasa seperti const colors = { red: "#f00" } adalah sebuah nilai. typeof colors mengubah nilai itu menjadi tipenya, lalu keyof typeof colors menghasilkan union dari key-nya, di sini "red". Menulis keyof colors gagal dengan error TS2749.

Kenapa keyof mengembalikan string | number?

Itu terjadi kalau tipenya punya string index signature seperti { [key: string]: number }. JavaScript mengubah key properti berupa angka menjadi string, jadi obj[42] juga akses yang valid, dan TypeScript menyertakan number di tipe key-nya. Pakai Extract<keyof T, string> atau string & keyof T kalau kamu hanya ingin key berupa string.

Kenapa Object.keys mengembalikan string[] dan bukan (keyof T)[]?

Karena sebuah object bisa menyimpan lebih banyak properti daripada yang tercantum di tipenya: nilai dengan field tambahan bisa di-assign ke tipe yang lebih sempit. Memberi tipe (keyof T)[] pada hasilnya akan menjadi kebohongan untuk object-object seperti itu. Kalau kamu tahu object-nya punya key yang persis sama dengan yang dideklarasikan, lakukan cast: Object.keys(obj) as (keyof typeof obj)[].

Bagaimana cara memperbaiki error "Element implicitly has an 'any' type because expression of type 'string' can't be used to index type"?

Indeksnya adalah string biasa, dan tipe object-nya tidak punya string index signature. Beri tipe keyof YourType pada parameternya alih-alih string, atau periksa string-nya lebih dulu dengan type guard yang mempersempitnya menjadi keyof YourType, atau beri object itu index signature atau tipe Record<string, V> kalau memang menerima key apa pun.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai