Menu

Mapped Type di TypeScript: Sintaks, Modifier, Key Remapping

Mapped type membangun tipe object baru dengan menelusuri key: { [K in keyof T]: ... }. Pelajari sintaksnya, modifier readonly dan ? dengan + dan -, key remapping dengan as, menyaring key, dan cara Partial, Readonly, Required, Pick, serta Record ditulis.

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

Mapped type membangun tipe object baru dengan menelusuri sekumpulan key. { [K in keyof T]: boolean } berarti "untuk setiap key K dari T, buat properti bernama K bertipe boolean":

Tambahkan sebuah key ke Features, dan Flags<Features> juga akan mewajibkannya. Mapped type hanya ada saat kompilasi: ia menjelaskan object, bukan membuatnya.

Sintaksnya

Mapped type punya tiga bagian: variabel key, union berisi key yang ditelusuri, dan tipe properti, yang boleh memakai variabel key tersebut.

type MappedType = {
  [K in Keys]: PropertyType; // K takes each member of Keys in turn
};

Keys boleh berupa union apa pun yang berisi string, angka, atau symbol. Tidak harus berasal dari keyof:

Itulah persis yang dilakukan Record<Size, number>; Record adalah mapped type satu baris. Tipe properti boleh memakai key-nya: { [K in keyof T]: T[K] } menyalin setiap tipe properti lewat indexed access, dan { [K in keyof T]: T[K] | null } membuat setiap properti bisa bernilai null.

Modifier: readonly dan ? dengan + dan -

Mapped type bisa menambah atau menghapus modifier readonly dan opsional (?) pada setiap properti sekaligus. Beri awalan + untuk menambah (ini default kalau kamu tidak menulis apa pun) dan - untuk menghapus:

Baris terakhir menunjukkan bahwa readonly hanyalah aturan saat kompilasi: assignment itu dilaporkan (dan di sini diredam dengan @ts-expect-error), tetapi JavaScript yang dihasilkan tetap menjalankannya. -? tidak hanya membuang tanda tanya: ia juga membuang undefined dari tipe properti, jadi AllRequired<Account> menolak { id: 1, email: undefined } dengan error TS2322: Type 'undefined' is not assignable to type 'string'.

Cara Partial, Readonly, Pick, dan Record Dibuat

Utility type bawaan yang mengubah bentuk object adalah mapped type. Ini definisinya dari lib.es5.d.ts milik TypeScript sendiri:

type Partial<T> = { [P in keyof T]?: T[P] };
type Required<T> = { [P in keyof T]-?: T[P] };
type Readonly<T> = { readonly [P in keyof T]: T[P] };
type Pick<T, K extends keyof T> = { [P in K]: T[P] };
type Record<K extends keyof any, T> = { [P in K]: T };

Membacanya adalah latihan yang bagus: Pick hanya menelusuri key yang kamu kirim, dan Record mengabaikan tipe sumber apa pun serta memberi setiap key tipe value yang sama. Omit bukan mapped type tersendiri: ia adalah Pick<T, Exclude<keyof T, K>>. Referensi utility type mencantumkan semuanya.

Key Remapping dengan as

Klausa as setelah key mengubah nama properti. Jika digabung dengan template literal type, klausa ini bisa membuat nama baru dari nama lama:

string & K ada di sana karena keyof T bisa berisi key bertipe number dan symbol, sedangkan Capitalize hanya menerima string. Intersection itu mempertahankan key berupa string dan membuang sisanya.

Menyaring Key dengan never

Kalau klausa as menghasilkan never untuk sebuah key, key itu dihapus. Dengan conditional type, kamu bisa mempertahankan atau membuang properti berdasarkan tipenya:

Tipe seperti DataOnly berguna untuk menjelaskan apa yang tersisa setelah JSON.stringify, atau apa yang diedit sebuah form: field-nya saja tanpa method.

Modifier Mana yang Dipertahankan

Mapped type atas keyof T untuk suatu tipe T disebut homomorphic, dan ia menyalin modifier readonly dan ? setiap properti dari T. Mapped type atas union key biasa dimulai tanpa modifier apa pun:

type Account = { readonly id: number; email?: string };

type Copy<T> = { [K in keyof T]: T[K] };
type A = Copy<Account>;
// { readonly id: number; email?: string }  (modifiers kept)

type B = { [K in "id" | "email"]: Account[K] };
// { id: number; email: string | undefined }  (email is required now)

B masih punya undefined di tipe email, karena Account["email"] memang mengandungnya, tetapi propertinya sendiri tidak lagi opsional: { id: 1 } ditolak. Itulah sebabnya Partial<T> dan kawan-kawannya menelusuri keyof T: mereka mempertahankan semua yang tidak mereka ubah secara eksplisit.

Mapped type homomorphic yang generic, jika diterapkan pada array atau tuple, menghasilkan array atau tuple, bukan object dengan key berupa angka. Readonly<string[]> adalah readonly string[], dan dengan type Box<T> = { [K in keyof T]: { value: T[K] } }, Box<[string, number]> adalah [{ value: string }, { value: number }].

Mapped Type dan Kode Runtime

Karena mapped type hanya menjelaskan sebuah object, fungsi yang membangun object seperti itu butuh perulangan runtime-nya sendiri. Tipe tersebut lalu menjelaskan hasilnya:

Callback-callback itu mendapat tipe parameternya dari mapped type (v bertipe string di yang satu dan number di yang lain) tanpa anotasi apa pun. Kalau kamu datang ke sini untuk mencari class Map, bukan pemetaan tipe, lihat Map di TypeScript.

Pertanyaan yang sering diajukan

Apa itu mapped type di TypeScript?

Tipe yang membuat tipe object dengan menelusuri sebuah union berisi key: { [K in Keys]: SomeType }. Paling sering key-nya adalah keyof T, jadi tipe baru itu punya key yang sama dengan T tetapi dengan tipe properti yang sudah diubah, seperti di type Flags<T> = { [K in keyof T]: boolean }.

Apa arti +readonly, -readonly, +? dan -? di mapped type?

Semuanya menambah atau menghapus modifier. readonly atau +readonly membuat setiap properti menjadi readonly, -readonly menghapus readonly. ? atau +? membuat setiap properti menjadi opsional, -? membuatnya wajib sekaligus membuang undefined dari tipenya. Required<T> ditulis dengan -?.

Bagaimana cara mengganti nama key di mapped type?

Pakai klausa as setelah key: { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] } mengubah name menjadi getName. Memetakan sebuah key ke never di klausa as akan menghapusnya, dan begitulah cara menyaring properti.

Apakah mapped type sama dengan Map di TypeScript?

Tidak. Mapped type adalah transformasi tipe saat kompilasi dan tidak menghasilkan kode apa pun. Map<K, V> adalah class Map JavaScript, sebuah koleksi pasangan key-value saat runtime. Pencarian "typescript map type" sering bermaksud salah satu dari keduanya.

Bagaimana Partial dan Readonly diimplementasikan?

Sebagai mapped type di standard library: type Partial<T> = { [P in keyof T]?: T[P] } dan type Readonly<T> = { readonly [P in keyof T]: T[P] }. Pick dan Record juga mapped type; Omit adalah Pick yang digabung dengan Exclude.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai