value as Type은 타입 단언으로, value를 Type으로 취급하라고 TypeScript에 알립니다. 흔히 캐스트라고 부르지만 컴파일러에 대한 지시일 뿐입니다. JavaScript 출력에서 지워지고, 런타임에 아무것도 변환하거나 검사하지 않습니다.
이것이 전형적인 용도입니다. 값에 대해 컴파일러보다 더 많이 알고 있을 때(여기서는 어떤 JSON의 모양) 그렇다고 말하는 것입니다. 틀렸다면 아무것도 경고해 주지 않습니다. 이어지는 절에서 그것이 무슨 뜻인지, 언제 런타임 검사가 더 나은 선택인지 보여 줍니다.
as와 꺾쇠괄호 문법
같은 단언을 쓰는 방법이 두 가지 있습니다.
const someValue: unknown = "hello";
const a = someValue as string; // as syntax
const b = <string>someValue; // angle-bracket syntax, same meaning
꺾쇠괄호 형태는 <string>이 JSX 태그로 읽히는 .tsx 파일에서 허용되지 않습니다. 어디서나 as를 쓰면 이런 고민이 생기지 않습니다. 단언은 약하게 결합하므로, 표현식을 이어 가려면 괄호로 감싸세요: (value as string).length.
단언은 값을 변환하지 않는다
실제 버그를 일으키는 부분입니다. 단언은 값 자체가 아니라 값에 대해 컴파일러가 믿는 것을 바꿉니다.
컴파일러는 asserted가 number라고 믿으므로 asserted + 1이 산술로 타입 검사를 통과합니다. 런타임에는 여전히 문자열 "42"이고 JavaScript는 문자열을 이어 붙입니다. 값의 타입을 바꾸려면 변환하세요: Number(x), String(x), Boolean(x), BigInt(x), new Date(x). 변환 함수 비교는 문자열을 숫자로 페이지에 있습니다.
| 하고 싶은 것 | 쓸 것 | 런타임 효과 |
|---|---|---|
| 알고 있는 타입을 컴파일러에 알림 | x as T | 없음 |
| 문자열을 숫자로 바꿈 | Number(x), parseInt(x, 10) | 변환함 |
| 무엇이든 문자열로 바꿈 | String(x), `${x}` | 변환함 |
| 먼저 타입을 확인함 | 타입 가드, typeof, instanceof | 검사함 |
컴파일러가 허용하는 것
as가 무제한은 아닙니다. TypeScript는 한 타입을 다른 타입에 대입할 수 있을 때 x as T를 허용합니다. 넓히기("a" as string, dog as Animal)와 좁히기(animal as Dog, unknown as User) 모두 괜찮습니다. 두 타입이 전혀 겹치지 않으면 거부합니다.
컴파일러는 index.ts(3,11): error TS2352: Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.를 보고합니다. 메시지 자체가 비상구를 알려 줍니다: input as unknown as number. 이 이중 단언은 컴파일되지만, 런타임에는 위의 예와 똑같이 틀립니다. 이것이 필요하다고 느껴진다면 대개 올바른 해결책은 변환(Number(input))이나 다른 타입입니다.
객체에 대해서는 겹침 규칙이 느슨합니다. 속성을 일부만 가진 객체 리터럴도 받아들여지며, 그래서 as는 불완전한 객체를 조용히 통과시킵니다.
표기(const draft: User = { name: "Ada" })나 satisfies User였다면 빠진 email을 보고했을 것입니다(TS2741). 객체 리터럴에 as를 쓰는 것은 정말로 나중에 채울 의도일 때만 하고, 완전한 객체를 만드는 편을 택하세요.
as const는 다르다
as const는 단언처럼 보이지만 느슨하게 하는 것과 반대로 동작합니다. 리터럴을 가능한 한 좁게 만듭니다. 문자열은 리터럴 타입으로 남고, 배열은 readonly 튜플이 되며, 객체 속성은 readonly가 됩니다.
컴파일러가 볼 수 없는 것을 주장하는 대신 리터럴을 정확히 기술하므로 안전합니다. (isSize 안의 sizes as readonly string[]는 넓히는 단언으로, 역시 안전합니다. includes가 아무 문자열이나 받게 해 줍니다.) 더 자세한 내용은 리터럴 타입을 보세요.
타입 가드가 더 나은 도구일 때
as는 주장이고, 타입 가드는 검사입니다. 코드 밖에서 데이터가 들어오는 경계(JSON, fetch, localStorage, 사용자 입력, 메시지)에서는 그 주장이 거짓일 수 있고, 단언은 경계에서의 명확한 오류를 다른 어딘가의 헷갈리는 오류로 바꿔 놓습니다.
비슷해 보이는 도구들의 대략적인 안내:
| 도구 | 컴파일 시점 검사 | 런타임 검사 | 쓰는 때 |
|---|---|---|---|
표기 const x: T = ... | 예, 완전히 | 아니요 | 값을 직접 만들 때 |
satisfies T | 예, 완전히, 추론된 타입 유지 | 아니요 | 객체 리터럴, 설정 |
as T | "타입이 겹치는가"만 | 아니요 | 컴파일러보다 많이 알 때 |
x! | null / undefined 제거만 | 아니요 | 값이 설정되어 있다는 것을 알 때 |
타입 가드 x is T | 가드의 본문은 평범한 코드 | 예 | 외부에서 온 데이터 |
as의 좋은 용도는 두 가지가 남습니다. 컴파일러가 따라갈 수 없는 것을 좁히는 경우(두 줄 위에서 설정한 Map 항목, 타입 없는 라이브러리의 값)와, 부분적인 픽스처를 만드는 테스트 코드입니다. 작게 유지하고, 그 주장이 참이라는 것을 아는 곳 가까이에 두세요.
자주 묻는 질문
TypeScript에서 as는 무엇을 하나요?
value as Type은 타입 단언입니다. 이제부터 value를 Type으로 취급하라고 컴파일러에 알립니다. 컴파일된 JavaScript에서 제거되므로 변환도 런타임 검사도 하지 않습니다. 단언이 틀렸다면 프로그램은 나중에, 잘못된 타입이 쓰이는 곳에서 실패합니다.
TypeScript에서 타입은 어떻게 캐스팅하나요?
TypeScript에는 런타임 캐스트가 없습니다. 컴파일러보다 더 많이 알 때 정적 타입을 바꾸려면 as(또는 예전 방식 <Type>value)를 쓰세요. 값을 실제로 변환하려면 함수를 호출하세요: Number("42"), String(42), Boolean(x), new Date(text).
TypeScript에서 "as unknown as"는 무슨 뜻인가요?
이중 단언입니다. 두 타입이 전혀 겹치지 않으면 TypeScript는 x as T를 거부하는데(오류 TS2352), 먼저 unknown을 거치면 그 검사를 우회합니다. 무엇이든 unknown으로, 또 unknown에서 단언할 수 있기 때문입니다. 그 값에 대한 타입 검사를 완전히 끄므로, 테스트나 다른 방법으로 타입을 검증한 코드에만 쓰세요.
TypeScript에서 as와 꺾쇠괄호의 차이는 무엇인가요?
의미는 같습니다. <string>value와 value as string은 같은 단언입니다. 꺾쇠괄호 형태는 JSX와 충돌하므로 .tsx 파일에서 쓸 수 없고, 그래서 모두가 as 형태를 씁니다.
as와 satisfies의 차이는 무엇인가요?
as는 추론된 타입을 덮어쓰고 검사는 아주 적게 합니다(빠진 속성도 허용됩니다). satisfies는 값을 타입에 대해 검사해서 빠지거나 남는 속성을 보고하고, 정확하게 추론된 타입을 유지합니다. 객체 리터럴에는 satisfies를 쓰고, as는 정말로 컴파일러보다 많이 알 때만 쓰세요.