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

keyof & Indexed Access

operator keyof รับ object type แล้วสร้าง union ของ key ออกมาเป็น type

interface User {
id: number;
name: string;
active: boolean;
}
type UserKey = keyof User;
// ^? "id" | "name" | "active"

keyof User ไม่ใช่ value — แต่เป็น union type "id" | "name" | "active" นี่คือวัตถุดิบสำหรับการวนซ้ำบน type ซึ่งก็คือสิ่งที่ mapped type ทำพอดี

สำหรับ object type ที่มี index signature keyof จะให้ index type ออกมา:

type Dict = { [key: string]: number };
type DictKey = keyof Dict;
// ^? string | number (JS object key เข้าถึงด้วย number ได้ด้วย)

พอมี key แล้ว indexed access จะ lookup type ของ value ที่ key นั้น — เวอร์ชันระดับ type ของ obj[key]

interface User {
id: number;
name: string;
}
type IdType = User["id"];
// ^? number
type NameType = User["name"];
// ^? string

index ด้วย union ของ key เพื่อเอา union ของ value type ก็ได้:

type Values = User[keyof User];
// ^? number | string

User[keyof User] อ่านว่า “type ของทุก value ใน User” — เป็น idiom ที่ใช้บ่อยเพื่อเอา union ของ value type ทั้งหมดของ type

flowchart LR
  t["User { id: number, name: string }"] -->|keyof| keys["keys: id or name"]
  keys -->|"T[key]"| vals["value types: number or string"]
keyof เอา key, indexed access เอา value type

array และ tuple ก็เป็น object type เหมือนกัน indexed access จึงใช้ได้ — และ [number] คือทริกสำหรับ “element type”

type Nums = number[];
type Elem = Nums[number];
// ^? number
const tuple = [1, "two", true] as const;
type TupleElem = (typeof tuple)[number];
// ^? 1 | "two" | true
type First = (typeof tuple)[0];
// ^? 1

Arr[number] แปลว่า “index array นี้ด้วย number อะไรก็ได้” ซึ่งได้ union ของ element type ทั้งหมด — เป็นวิธีมาตรฐานในการเปลี่ยน array/tuple type ให้เป็น union

แทบทุก utility type สร้างจาก keyof + indexed access + mapped type:

// Pick เขียนเอง — วนซ้ำ key ที่เลือก, lookup value type แต่ละตัว
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
type NameOnly = MyPick<User, "name">;
// ^? { name: string }

K extends keyof T จำกัด key ให้เป็น key จริงของ T; [P in K] วนซ้ำ key เหล่านั้น (mapped type, บทเรียนถัดไปถ้าคุณข้ามมา); T[P] lookup value type แต่ละตัว สาม primitive ได้เครื่องมือที่มีประโยชน์หนึ่งตัว

`keyof User` ให้อะไรออกมาสำหรับ `interface User { id: number; name: string }`?
indexed access type `User["name"]` ให้อะไร?
จะเอา element type ของ array type `T = string[]` อย่างไร?
`User[keyof User]` แทนอะไร?