value satisfies Type은 컴파일 시점에 value가 Type에 맞는지 검사하고, 그다음 값 자체의 더 정밀한 타입은 건드리지 않습니다. 타입 표기라면 그 정밀한 타입을 Type으로 대체했을 것입니다. satisfies는 넓히지 않고 검증합니다.
satisfies도 검사는 합니다. 빠진 색상, bleu 같은 철자 틀린 키, true 같은 값은 그 줄에서 컴파일 오류입니다. TypeScript 4.9부터 있었고, 모든 타입 표기처럼 출력된 JavaScript에서는 제거됩니다.
satisfies가 해결하는 문제
타입 표기를 쓰면 변수의 타입은 곧 그 표기입니다. 컴파일러는 리터럴에서 본 것을 잊습니다. 여기서는 같은 팔레트에 타입 표기를 붙였고, 이제 TypeScript는 green이 문자열이라는 것을 더 이상 모릅니다.
컴파일러는 이렇게 보고합니다.
index.ts(11,27): error TS2339: Property 'toUpperCase' does not exist on type 'Color'.
Property 'toUpperCase' does not exist on type '[number, number, number]'.
TypeScript 4.9 전에는 선택지가 두 가지였습니다. 표기를 붙이고 곳곳에서 손으로 좁히거나(typeof palette.green === "string"), 표기를 생략하고 검사를 포기하는 것입니다. satisfies는 둘 다 줍니다. : Record<ColorName, Color>를 닫는 중괄호 뒤의 satisfies Record<ColorName, Color>로 바꾸면 실행됩니다.
satisfies, 타입 표기, as 비교
같은 설정 객체를 세 가지 방식으로 썼습니다.
as는 빠진 lang을 통과시켰고, asserted.lang은 타입이 string이라고 말하는데도 런타임에는 undefined입니다. 다른 두 줄에서 lang을 지우면 둘 다 TS2741, Property 'lang' is missing in type ...으로 실패합니다.
타입 표기 const x: T = v | 단언 v as T | v satisfies T | |
|---|---|---|---|
| 빠진 속성 | 오류 | 허용 | 오류 |
| 남는 속성(객체 리터럴) | 오류 | 허용 | 오류 |
| 잘못된 속성 타입 | 오류 | 타입이 겹치지 않을 때만 | 오류 |
이후 x의 타입 | T | T | v의 추론된 타입 |
리터럴 타입("dark", 8080) | T로 넓어짐 | T로 넓어짐 | T가 허용하는 곳에서 유지 |
Record<string, ...>의 키 | 아무 문자열(오타도 컴파일됨) | 아무 문자열 | 정확히 쓴 키들 |
| 런타임 효과 | 없음 | 없음 | 없음 |
경험칙: 변수가 선언된 타입을 갖기를 원할 때(다시 대입할 값, 공개 API)는 타입을 표기하고, 검사는 원하지만 값 자체의 타입이 더 유용할 때는 satisfies를 쓰세요.
객체 리터럴의 실수 잡기
satisfies는 초과 속성 검사를 포함한 완전한 대입 가능성 검사를 수행하므로, 키의 오타는 오류가 됩니다.
type Route = { path: string; method: "GET" | "POST" };
const home = { path: "/", metod: "GET" } satisfies Route;
// error TS2561: Object literal may only specify known properties, but 'metod' does not exist in type 'Route'. Did you mean to write 'method'?
검사는 타입 표기처럼 리터럴에 문맥적 타입 도 줍니다. 이것은 두 가지 면에서 중요합니다. 대상 타입이 기대하면 문자열 리터럴이 리터럴 타입으로 유지됩니다. { path: "/", method: "GET" } satisfies Route는 method: "GET"을 갖지만, 표기 없는 같은 객체라면 method: string으로 추론될 것입니다. 그리고 콜백 매개변수가 대상 타입에서 추론됩니다.
Record의 키가 알려진 채로 남는다
흔한 용도는 조회 테이블입니다. Record<string, T>로 표기하면 모든 문자열이 유효한 키이고, 오타도 컴파일되어 런타임에 undefined를 반환합니다. satisfies를 쓰면 값은 여전히 T에 대해 검사되지만, 변수의 타입은 정확히 작성한 키들을 나열합니다.
keyof typeof endpoints가 쓸모 있는 것은 키가 살아남았기 때문입니다. 타입 표기였다면 그냥 string이었을 것입니다.
정해진 키 집합을 요구하려면 유니언에 대한 Record를 만족시키세요. satisfies Record<"dev" | "prod", string>은 빠진 prod를 TS2741로, 알 수 없는 staging을 TS2353으로 보고합니다.
as const satisfies
as const와 satisfies는 함께 쓸 수 있습니다. as const를 먼저 쓰세요. 값을 리터럴 타입을 가진 깊은 readonly로 만들고, 그다음 satisfies가 그 정확한 값을 검사합니다.
각 라우트는 Route에 대해 검사되고(method: "PUT"은 오류입니다), 리터럴 타입의 튜플은 계속 쓸 수 있으므로 Path는 실제 경로들의 유니언입니다. as const 배열은 readonly이므로 대상으로는 readonly Route[](또는 ReadonlyArray<Route>)를 쓰세요.
설정 객체
설정은 satisfies가 제 몫을 하는 곳입니다. 모양은 맞아야 하고, 다른 곳의 코드는 정확한 값을 원합니다.
production 항목을 빠뜨리거나, logLevel의 철자를 틀리거나, logLevel: "verbose"라고 쓰면 컴파일러가 정확한 줄을 가리킵니다. 같은 패턴은 *.config.ts 파일에도 잘 맞습니다. export default { ... } satisfies SomeConfig는 파일 전체를 검사하면서도 export된 객체가 리터럴 값을 유지합니다.
satisfies를 쓰지 말아야 할 때
- 변수를 다시 대입할 때.
let cfg = { port: 3000 } satisfies { port: number | string }은cfg에{ port: number }타입을 주므로, 나중의cfg = { port: "80" }은 실패합니다(TS2322). 바꿀 변수에는 타입을 표기하세요. - 일부러 선언된 타입을 원할 때. 함수 반환값이나 API의 일부인 export 상수라면 표기의 타입이 계약이고, 정확한 리터럴 타입을 노출하면 나중의 변경이 호환성을 깨뜨릴 수 있습니다.
- 값이 리터럴이 아닐 때.
satisfies는 객체와 배열 리터럴에서 빛납니다. 변수나 호출 결과에 쓰면 평범한 대입 가능성 검사일 뿐이며, 타입 표기가 이미 그것을 줍니다.
자주 묻는 질문
TypeScript에서 satisfies는 무엇을 하나요?
expression satisfies Type은 표현식이 Type에 대입 가능한지 컴파일 시점에 검사해서 빠진 속성, 남는 속성, 잘못된 값 타입을 보고하고, 표현식 자체의 추론된 타입은 그대로 둡니다. 타입 표기의 안전성과 추론의 정밀함을 함께 얻습니다. JavaScript 출력에서는 지워집니다.
satisfies와 타입 표기의 차이는 무엇인가요?
둘 다 값을 검사합니다. 타입 표기(const x: T = ...)는 그다음 변수에 T 타입을 주고, 컴파일러가 값에 대해 알던 것(리터럴 타입, 각 속성이 유니언의 어느 멤버인지, 어떤 키가 있는지)을 잊습니다. satisfies T는 추론된 타입을 유지하므로 x.someKey가 존재한다는 것을 알고, 문자열을 담은 string | number 속성은 string 타입이 됩니다.
TypeScript에서 satisfies와 as의 차이는 무엇인가요?
as는 단언입니다. 타입을 덮어쓰고 거의 아무것도 검사하지 않으므로 빠진 속성을 알아채지 못합니다. satisfies는 검사입니다. 값이 정말로 타입에 맞아야 하고, 자신의 추론된 타입이 유지됩니다. 둘 다 컴파일된다면 satisfies가 더 안전한 선택입니다.
as const satisfies는 무슨 뜻인가요?
둘 다 적용합니다. as const가 값을 리터럴 타입을 가진 깊은 readonly로 만들고, 그다음 satisfies가 그 결과를 타입에 대해 검사합니다. as const를 먼저 쓰세요: const routes = [...] as const satisfies readonly Route[];. 변수는 나중에 쓸 정확한 리터럴 타입을 유지하고, 잘못된 항목은 여전히 컴파일 오류입니다.
satisfies는 어느 TypeScript 버전에서 추가되었나요?
2022년 11월에 출시된 TypeScript 4.9입니다. 지울 수 있는 평범한 문법이므로 Node의 내장 타입 제거에서도 동작하고, 현재의 모든 TypeScript 버전(7 포함)이 지원합니다.