ข้ามไปยังเนื้อหา

Mapped Types

mapped type วนซ้ำบน key ของ type แล้วสร้าง type ใหม่ syntax คล้าย object type แต่ใส่ [K in ...] ตรงตำแหน่งที่ key ควรอยู่ — เป็น for loop ระดับ type

type Flags = {
[K in "a" | "b" | "c"]: boolean;
};
// ^? { a: boolean; b: boolean; c: boolean }

K ไล่ไปทีละ member ของ union "a" | "b" | "c" และสำหรับแต่ละตัว type ก็สร้าง property ออกมา รวมกับ keyof แล้วคุณวนซ้ำบน type ที่มีอยู่ได้:

interface User { id: number; name: string; }
type Stringify = {
[K in keyof User]: string;
};
// ^? { id: string; name: string }

ภายใน loop T[K] (indexed access) ให้ value type เดิม — คุณจึง transform แทนที่จะแทนที่ทั้งหมดได้:

type Getters<T> = {
[K in keyof T]: () => T[K];
};
type UserGetters = Getters<User>;
// ^? { id: () => number; name: () => string }

แต่ละ property กลายเป็น function ที่คืน value type นั้น รูปแบบ “เก็บ key ไว้ transform value” คือการใช้ mapped type ในชีวิตประจำวัน

mapped type เปลี่ยน modifier ของแต่ละ property ได้ด้วย คุณเพิ่ม readonly หรือ ? ด้วยการเขียนลงไปตรง ๆ และ ลบ ด้วย prefix -

// ทำให้ทุก property เป็น optional (นี่คือวิธีที่ Partial ทำงาน)
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
// ทำให้ทุก property เป็น readonly (นี่คือ Readonly)
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};
// ถอด optional และ readonly ออกจากทุก property
type Concrete<T> = {
-readonly [K in keyof T]-?: T[K];
};
flowchart LR
  src["T: { id: number, name: string }"] -->|"for each K in keyof T"| loop["apply transform:
modifier + value type"]
  loop --> out["new type:
{ id?: number, name?: string }"]
mapped type transform แต่ละ property

-? ลบ optional และ -readonly ลบ immutability — นั่นคือวิธีที่ Required และ utility แบบ mutable-clone ถูกเขียนพอดี เวลาเห็น -? ใน library แปลว่า “บังคับให้ทุก property มีอยู่จริง”

feature ที่ใหม่กว่าให้คุณ rename หรือ filter key ระหว่าง map ด้วย clause as นี่คือวิธีสร้างอย่าง type “getters” ที่มีชื่อขึ้นต้น get หรือ drop key ที่ value type ไม่ต้องการ:

// rename แต่ละ key เป็น get<Key>
type Getters2<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type UG = Getters2<{ name: string }>;
// ^? { getName: () => string }
// filter: เก็บเฉพาะ property ที่ value เป็น string (remap ตัวที่ไม่เอาเป็น never)
type StringProps<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};

การ remap key เป็น never จะ ลบ key นั้นออกจากผลลัพธ์ — เป็นทริกมาตรฐานสำหรับ filter property ตาม value type

mapped type คืออะไร?
ภายใน `{ [K in keyof T]: ... }` จะอ้างถึง value type เดิมที่ key K อย่างไร?
modifier `-?` ใน mapped type ทำอะไร?
ใน clause `as` ของ key remapping เกิดอะไรขึ้นเมื่อ remap key เป็น `never`?