TypeScript에서 typeof는 두 가지를 뜻합니다. 표현식에서는 JavaScript 연산자입니다. 실행되어 "string"이나 "number" 같은 문자열을 반환하고, 그 문자열과 비교하면 TypeScript가 변수를 좁힙니다. 타입 위치에서는 타입 쿼리입니다. typeof config는 변수 config의 정적 타입을 줍니다.
마지막 줄이 차이를 보여 줍니다. console.log 안의 typeof custom은 런타임 연산자이고 object를 출력하지만, type Options = 뒤의 typeof defaults는 컴파일 시점의 쿼리여서 JavaScript에 도달하지 않습니다.
두 가지 의미 한눈에 보기
런타임 typeof(JavaScript) | 타입 쿼리 typeof(TypeScript) | |
|---|---|---|
| 나타나는 곳 | 모든 표현식: if, return, console.log(...) | 타입 위치: : 뒤, type X = ... 안, <...> 안 |
| 예 | typeof x === "string" | let y: typeof x; |
| 평가 시점 | 코드가 실행될 때 | 컴파일러가 평가한 뒤 지워짐 |
| 결과 | 여덟 가지 문자열 중 하나 | 타입 |
| 용도 | 값을 확인하고 타입을 좁히기 | 값에서 타입 끌어내기 |
TypeScript는 위치로 둘을 구분하므로, 같은 키워드가 동시에 두 가지를 뜻하는 일은 없습니다.
런타임 typeof의 결과
런타임 연산자는 여덟 가지 문자열 중 하나를 반환합니다. 흔한 값들이 무엇을 주는지 실행해 보세요.
| 값 | typeof 결과 | 참고 |
|---|---|---|
"hi", 템플릿 문자열 | "string" | |
42, 3.14, NaN, Infinity | "number" | NaN도 숫자이므로 Number.isNaN으로 확인 |
10n | "bigint" | |
true, false | "boolean" | |
undefined | "undefined" | |
Symbol("id") | "symbol" | |
| 함수, 화살표 함수, 클래스 | "function" | 클래스는 생성자 함수 |
null | "object" | JavaScript의 역사적인 버그 |
{}, [], new Date(), new Map() | "object" | 배열도 객체이므로 Array.isArray 사용 |
new String("x"), new Number(1) | "object" | 래퍼 객체, 쓰지 마세요 |
평범한 JavaScript에서 선언된 적 없는 변수에 typeof를 쓰면 예외 대신 "undefined"를 반환합니다. TypeScript에서는 그런 코드가 컴파일되지 않습니다: Cannot find name 'notDeclared'.(TS2304).
typeof로 좁히기
if, switch, 삼항 연산자, && 뒤의 typeof 비교는 각 분기에서 변수를 좁힙니다. 원시 타입의 유니언을 다루는 표준 방법이자 unknown에 가장 먼저 쓸 검사입니다.
typeof x !== "string"은 반대로 string을 제외한 모든 것으로 좁힙니다. 그리고 컴파일러는 가능한 여덟 가지 문자열을 알기 때문에, 비교의 오타는 조용히 항상 거짓이 되는 검사가 아니라 컴파일 오류가 됩니다.
컴파일러는 index.ts(3,7): error TS2367: This comparison appears to be unintentional because the types '"bigint" | "boolean" | "function" | "number" | "object" | "string" | "symbol" | "undefined"' and '"strnig"' have no overlap.를 출력합니다. 평범한 JavaScript라면 이 오타 때문에 모든 문자열이 조용히 number 경로로 갔을 것입니다. 철자를 고치면 text를 출력합니다.
typeof null 함정
typeof null이 "object"이므로 TypeScript는 typeof x === "object"를 object가 아니라 object | null로 좁힙니다. null 검사를 더하지 않으면 속성을 읽는 것이 오류입니다(TS18047 'x' is possibly 'null'.).
typeof는 배열, 날짜, 맵, 평범한 객체를 구분하지 못합니다. 모두 "object"입니다. 배열에는 Array.isArray(x)를, 클래스 인스턴스에는 x instanceof Date를 쓰세요. 인터페이스는 지워지므로 typeof든 다른 무엇이든 런타임에 값이 인터페이스에 맞는지 확인할 수 없습니다. 그러려면 속성을 확인하는 타입 가드 함수를 작성하세요.
타입 위치의 typeof
타입에서 typeof someVariable은 TypeScript가 그 변수에 대해 추론한(또는 선언된) 타입을 복사합니다. 이미 존재하는 값의 타입을 손으로 쓰는 수고를 덜고, 둘을 동기화된 상태로 유지합니다.
const theme = {
primary: "#3178c6",
spacing: 8,
dark: false,
};
type Theme = typeof theme;
// { primary: string; spacing: number; dark: boolean }
function withSpacing(t: Theme, factor: number): Theme {
return { ...t, spacing: t.spacing * factor };
}
console.log(withSpacing(theme, 2)); // { primary: '#3178c6', spacing: 16, dark: false }
let userName = "Ada";
const fixed = "Ada";
type A = typeof userName; // string
type B = typeof fixed; // "Ada" (a const keeps its literal type)
결과는 변수를 어떻게 선언했는지에 따라 다릅니다. let이나 객체 속성은 넓어지고(string, number), const 원시 값은 리터럴 타입을 유지합니다. 객체나 배열에 as const를 붙이면 typeof는 끝까지 readonly 리터럴 타입을 줍니다. typeof는 속성 접근도 받아서 typeof theme.spacing은 number이지만, 임의의 표현식은 받지 않습니다. type T = typeof getTheme();는 문법 오류(';' expected., TS1005)입니다. 호출이 반환하는 타입에는 ReturnType<typeof getTheme>을 쓰세요.
keyof typeof: 객체의 키를 타입으로
keyof typeof obj는 가장 흔한 조합입니다. typeof가 객체를 타입으로 바꾸고, keyof가 키를 가져와서, 객체가 바뀌어도 정확하게 유지되는 속성 이름의 유니언을 얻습니다.
상수 배열에 같은 기법을 쓰면 값들의 유니언을 얻습니다. const roles = ["admin", "editor"] as const라면 타입 (typeof roles)[number]는 "admin" | "editor"입니다. as const가 없으면 그냥 string일 것입니다. keyof 자체는 keyof 페이지에서 다룹니다.
ReturnType<typeof fn>과 클래스의 typeof
ReturnType과 Parameters 같은 유틸리티 타입은 함수 타입을 받습니다. 함수 이름은 값이므로 먼저 typeof를 거쳐야 합니다.
ReturnType<createUser>라고 쓰면 'createUser' refers to a value, but is being used as a type here. Did you mean 'typeof createUser'?(TS2749)로 실패하는데, 컴파일러가 바로 이것을 요구하는 것입니다.
클래스는 이름이 값이면서 타입이기도 한 유일한 경우입니다. 타입으로서 User는 클래스의 인스턴스를 뜻하고, typeof User는 정적 멤버를 포함한 클래스 자체, 즉 생성자를 뜻합니다.
class Point {
static origin = new Point(0, 0);
constructor(public x: number, public y: number) {}
}
const p: Point = new Point(1, 2); // an instance
const Ctor: typeof Point = Point; // the class (constructor + statics)
const q = new Ctor(3, 4); // q: Point
type Instance = InstanceType<typeof Point>; // Point
typeof, instanceof, 타입 가드 비교
| 검사 | 쓸 수 있는 대상 | 예 |
|---|---|---|
typeof | 원시 타입, 함수, "객체인가" | typeof x === "number" |
Array.isArray | 배열 | Array.isArray(x) |
instanceof | 클래스 인스턴스(Date, Error, 직접 만든 클래스) | x instanceof Date |
in | 속성 기준의 객체 유니언 | "email" in x |
| 타입 가드 함수 | 인터페이스, 타입 별칭, 그 밖의 모든 것 | x is User를 반환하는 isUser(x) |
맞는 것 중 가장 좁은 도구를 고르세요. 클래스 인스턴스는 instanceof를 보세요. 인터페이스로 기술된 모양이라면 쓸 수 있는 런타임 검사는 타입 가드 함수뿐입니다.
자주 묻는 질문
TypeScript에서 typeof는 무엇을 하나요?
나타나는 위치에 따라 서로 다른 두 가지 일을 합니다. 표현식에서 typeof x는 JavaScript 연산자입니다. 실행되어 "string"이나 "object" 같은 문자열을 반환하고, 그 문자열과 비교하면 TypeScript가 x를 좁힙니다. 타입 표기에서 typeof x는 타입 쿼리입니다. 컴파일러가 평가해서 변수 x의 정적 타입을 주고, 출력된 JavaScript에서는 사라집니다.
TypeScript에서 keyof typeof는 무엇인가요?
keyof typeof obj는 객체의 속성 이름들을 문자열 리터럴 타입의 유니언으로 줍니다. typeof obj가 객체 값을 타입으로 바꾸고, keyof가 그 타입의 키를 가져옵니다. const colors = { red: "#f00", blue: "#00f" }라면 keyof typeof colors는 "red" | "blue"입니다.
typeof null은 왜 "object"를 반환하나요?
JavaScript 첫 버전의 버그로, 이제는 웹을 깨뜨리지 않고는 고칠 수 없습니다. TypeScript는 이를 그대로 반영합니다. typeof x === "object" 뒤에 x는 object | null로 좁혀지므로, 속성을 읽기 전에 x !== null도 확인해야 합니다.
TypeScript에서 값이 배열인지 어떻게 확인하나요?
배열 타입으로 좁혀 주는 Array.isArray(value)를 쓰세요. typeof로는 할 수 없습니다. typeof []는 평범한 객체나 null과 똑같이 "object"입니다.
TypeScript에서 typeof와 instanceof의 차이는 무엇인가요?
typeof는 값의 원시 범주("string", "number", "function", "object"...)를 확인합니다. instanceof는 객체가 Date나 직접 만든 class User 같은 특정 클래스나 생성자로 만들어졌는지 확인합니다. 원시 타입에는 typeof를, 클래스 인스턴스에는 instanceof를 쓰세요. 둘 다 런타임에 존재하지 않는 인터페이스나 타입 별칭은 확인할 수 없습니다.