TypeScript에서 타입을 처음부터 작성하지 않고, 기존 타입이나 값에서 새 타입을 파생하는 방법이 있습니다. keyof와 typeof는 이 파생 작업의 핵심 연산자입니다.
객체 타입의 모든 키를 유니온 타입으로 반환합니다.
interface User {
id: number;
name: string;
email: string;
}
type UserKey = keyof User;
// "id" | "name" | "email"
keyof를 제네릭 제약과 함께 쓰면, 존재하는 키만 허용하는 함수를 만들 수 있습니다.
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { id: 1, name: "철수", email: "chulsoo@example.com" };
const name = getProperty(user, "name"); // string
const id = getProperty(user, "id"); // number
getProperty(user, "age"); // 오류: "age"는 User의 키가 아닙니다
반환 타입 T[K]는 실제 프로퍼티의 타입이 됩니다. "name"을 요청하면 string, "id"를 요청하면 number가 반환됩니다.
인덱싱 접근 타입(Indexed Access Type): T[K] 형태로 타입의 특정 프로퍼티 타입을 가져오는 문법.
배열 타입에 keyof를 쓰면 숫자 인덱스와 배열 메서드 이름을 포함한 유니온이 나옵니다.
type Arr = string[];
type ArrKeys = keyof Arr; // number | "length" | "push" | "pop" | ...
배열 원소의 타입을 꺼낼 때는 T[number]를 씁니다.
type Colors = ["red", "green", "blue"];
type Color = Colors[number]; // "red" | "green" | "blue"
값(변수, 함수, 객체)에서 타입을 추출합니다. 타입 선언 없이 작성한 값에서 타입을 파생할 때 유용합니다.
const config = {
host: "localhost",
port: 3000,
debug: true,
};
type Config = typeof config;
// { host: string; port: number; debug: boolean }
별도 타입 선언 없이 config를 기준으로 타입을 정의할 수 있습니다. 이후 config의 구조가 바뀌면 Config도 자동으로 갱신됩니다. 타입 위치에서 쓰이는 typeof는 값의 타입을 가져오며, 런타임의 typeof(문자열 반환)와 구분됩니다.
function createUser(name: string, age: number) {
return { name, age, id: Math.random() };
}
type CreateUserFn = typeof createUser;
// (name: string, age: number) => { name: string; age: number; id: number }
type UserResult = ReturnType<typeof createUser>;
// { name: string; age: number; id: number }
값에서 타입을 꺼내고, 그 키를 추출하는 조합입니다. 상수 객체를 타입의 기준으로 삼을 때 자주 씁니다.
const COLORS = {
red: "#FF0000",
green: "#00FF00",
blue: "#0000FF",
} as const;
type ColorKey = keyof typeof COLORS;
// "red" | "green" | "blue"
type ColorValue = typeof COLORS[ColorKey];
// "#FF0000" | "#00FF00" | "#0000FF"
function getColor(key: ColorKey): string {
return COLORS[key];
}
getColor("red"); // 정상
getColor("yellow"); // 오류: '"yellow"'는 'ColorKey'에 할당할 수 없습니다
as const: 객체나 배열을 리터럴 타입으로 추론하게 하는 단언. 프로퍼티 값을 리터럴 타입으로 좁히고 readonly로 만들어줍니다.
TypeScript의 enum 대신 as const 객체 + keyof typeof를 쓰는 패턴입니다. 런타임 코드가 없고 트리 셰이킹에 유리합니다.
const Direction = {
Up: "UP",
Down: "DOWN",
Left: "LEFT",
Right: "RIGHT",
} as const;
type Direction = typeof Direction[keyof typeof Direction];
// "UP" | "DOWN" | "LEFT" | "RIGHT"
function move(dir: Direction) {
console.log(dir);
}
move(Direction.Up); // 정상
move("UP"); // 정상
move("up"); // 오류
keyof와 typeof를 개별로 써도 유용하지만, 조합하면 코드에 이미 존재하는 값으로부터 타입을 파생하는 강력한 패턴이 됩니다. 타입과 값을 이중으로 관리할 필요 없이, 값을 수정하면 타입도 자동으로 따라갑니다.