[TypeScript] keyof 타입 연산: 객체 키를 활용한 타입 정의

keyof란

`keyof`는 객체 타입을 받아 그 속성 이름들로 구성된 유니언 타입을 생성합니다.

interface Coord {
  x: number;
  y: number;
}

type CoordKeys = keyof Coord; // "x" | "y"

function getCoordValue(key: CoordKeys, coord: Coord): number {
  return coord[key];
}

const point: Coord = { x: 10, y: 20 };
console.log(getCoordValue("x", point)); // 출력: 10
console.log(getCoordValue("y", point)); // 출력: 20

인덱스 시그니처와 keyof

객체가 문자열 또는 숫자 인덱스 시그니처를 가질 경우, `keyof`는 해당 인덱스 타입을 반환합니다.

숫자 인덱스 시그니처

interface NumberMap {
  [index: number]: string;
}

type NumberKeys = keyof NumberMap; // number

const numMap: NumberMap = { 0: "zero", 1: "one" };
function getNumValue(key: NumberKeys): string {
  return numMap[key];
}

console.log(getNumValue(0)); // 출력: zero

문자열 인덱스 시그니처

interface StringMap {
  [key: string]: boolean;
}

type StringKeys = keyof StringMap; // string | number

const strMap: StringMap = { active: true, "10": false };
function checkStatus(key: StringKeys): boolean {
  return strMap[key];
}

console.log(checkStatus("active")); // 출력: true
console.log(checkStatus(10));       // 출력: false

객체의 속성 동적 접근

interface Settings {
  theme: string;
  fontSize: number;
  darkMode: boolean;
}

type SettingKey = keyof Settings;

function updateSetting<K extends SettingKey>(key: K, value: Settings[K], settings: Settings): void {
  settings[key] = value;
}

const appSettings: Settings = { theme: "light", fontSize: 14, darkMode: false };
updateSetting("theme", "dark", appSettings);
updateSetting("darkMode", true, appSettings);

console.log(appSettings); // 출력: { theme: "dark", fontSize: 14, darkMode: true }

keyof와 매핑된 타입의 조합

type ReadonlyType<T> = {
  readonly [K in keyof T]: T[K];
};

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

type ReadonlyUser = ReadonlyType<User>;

const user: ReadonlyUser = { name: "지민", age: 25 };
// user.name = "수진"; // 오류: 읽기 전용
console.log(user.name); // 출력: 지민

제네릭을 사용하여 모든 속성을 읽기 전용으로 만듭니다.