satisfies 연산자 — as와 무엇이 다른가, 언제 써야 하는가
타입 단언과 검증의 차이를 이해하면 객체 리터럴 추론을 잃지 않으면서도 오타와 구조 오류를 안전하게 잡을 수 있습니다. as, satisfies, as const의 적절한 조합을 정리합니다.
TypeScript에서 객체 리터럴을 다룰 때 자주 등장하는 두 키워드, as와 satisfies. 이름은 비슷해 보이지만 동작은 정반대에 가깝습니다. as는 “이 값을 이 타입으로 봐줘”라는 강제 단언이고, satisfies는 “이 값이 이 타입에 맞는지 검사해줘, 단 추론된 좁은 타입은 그대로 유지해줘”라는 검증입니다.
1. as의 한계
as는 컴파일러에게 타입을 우기는 도구입니다. 잘 맞을 때는 편하지만, 한 발만 어긋나도 런타임에 문제가 됩니다.
1type Theme = { primary: string; secondary: string }; 2 3const t = { 4 primary: '#8b5cf6', 5 secondery: '#06b6d4', // 오타 6} as Theme; 7 8t.secondary; // 타입은 string이지만 런타임은 undefined
오타가 있어도 as는 통과시킵니다. 또 다른 문제는 추론 손실입니다.
1const status = { 2 ok: 200, 3 notFound: 404, 4} as Record<string, number>; 5 6status.ok; // number — 200이 아니라 그냥 number
리터럴 정보가 사라져서 200 같은 정확한 값이 뒤로 전달되지 않습니다.
2. satisfies의 동작
satisfies는 “이 객체가 타입에 맞는지만 확인하고, 실제 추론은 가장 좁게 유지”합니다.
1const status = { 2 ok: 200, 3 notFound: 404, 4} satisfies Record<string, number>; 5 6status.ok; // 200 (literal) 7status.notFound; // 404 (literal)
오타도 잡습니다.
1type Theme = { primary: string; secondary: string }; 2 3const t = { 4 primary: '#8b5cf6', 5 secondery: '#06b6d4', 6} satisfies Theme; 7// Object literal may only specify known properties, and 'secondery' does not exist in type 'Theme'.
핵심은 두 가지 — 검증은 하되, 추론된 좁은 타입(literal·tuple)은 보존한다는 점입니다.
3. 자주 쓰는 패턴
3.1 라우트 맵
1const routes = { 2 home: '/', 3 blog: '/blog', 4 post: (id: number) => `/blog/${id}`, 5} satisfies Record<string, string | ((...args: any[]) => string)>; 6 7routes.home; // '/' (literal) 8routes.post(1); // string
as로 했다면 routes.home이 string이 되어 다른 곳에서 비교할 때 타입이 약해집니다.
3.2 zod 스키마와의 합
1const userSchema = { 2 id: z.string(), 3 name: z.string(), 4} satisfies Record<string, z.ZodTypeAny>;
각 키의 정확한 zod 타입(예: ZodString)이 그대로 살아 추론에 활용됩니다.
3.3 const assertion과 함께
1const colors = { 2 primary: '#8b5cf6', 3 heart: '#f43f5e', 4} as const satisfies Record<string, `#${string}`>;
as const로 readonly + literal로 굳히고, satisfies로 형식만 검증합니다. 두 도구가 충돌 없이 협력합니다.
4. 주의할 점
satisfies는 타입 캐스팅이 아니다. 타입을 바꾸지 않고 검증만 합니다.- 변수 타입이 필요하면 별도로 적어야 합니다.
satisfies만으론 변수의 타입이 좁은 추론값이 되므로, 라이브러리 export에는 명시적 타입과 함께 쓰는 게 안전합니다. - 함수 매개변수 위치에서는 사용 불가. 객체 리터럴이나 변수 초기화에서만 의미가 있습니다.
5. 정리
| 도구 | 역할 | 추론 |
|---|---|---|
as T | 단언 — “이 값을 T로 봐줘” | T로 넓힘 |
satisfies T | 검증 — “이 값이 T에 맞는가” | 좁은 추론 유지 |
오타·구조 검증이 필요하면 거의 모든 경우 satisfies가 더 안전합니다. as는 외부 라이브러리 타입이 너무 느슨해서 강제로 좁혀야 하거나, unknown을 알려진 타입으로 풀 때만 남깁니다.
참고: TypeScript 4.9 릴리스 노트 / TypeScript Handbook의 satisfies 섹션.