blog

TypeScript의 is와 satisfies: 언제 무엇을 써야 할까

Front-end / TypeScript#TypeScript#type predicate#satisfies#타입 좁히기

value is User와 config satisfies Config는 둘 다 타입 이름 옆에 붙지만, 쓰는 위치와 해결하는 문제가 다르다.

  • is: 검사 함수가 참을 반환했을 때 값의 타입을 좁힌다
  • satisfies: 작성한 표현식이 특정 타입 조건을 만족하는지 확인한다

is: 검사 결과를 타입 좁히기에 연결

함수 반환 타입 자리의 매개변수 is 타입은 타입 서술어(type predicate) 다.

const isString = (value: unknown): value is string => typeof value === 'string';

const printUpperCase = (value: unknown) => {
  if (isString(value)) {
    console.log(value.toUpperCase()); // 이 분기에서 value는 string
  }
};

value is string 자체가 검사를 실행하지는 않는다. 실제 검사는 함수 본문(typeof)이 하고, TypeScript는 선언된 서술어를 믿고 if 분기 안의 타입을 좁힌다.

그래서 서술어가 실제 검사와 다르면 위험하다.

const isNumber = (value: unknown): value is number => true;

if (isNumber('문자열')) {
  // TypeScript는 number로 취급하지만 실제 값은 문자열
}

타입 서술어는 런타임 검사 결과를 타입 시스템에 전달하는 약속이다. 검사 로직이 맞는지는 TypeScript가 보장하지 않는다. (TypeScript 5.5부터는 (x) => x !== null처럼 단순한 함수의 서술어를 자동으로 추론해 주기도 한다.)

배열 필터링에서도 자주 쓴다.

const users = items.filter((item): item is User => item.type === 'user');
// users: User[]

satisfies: 조건을 확인하면서 구체적인 타입 유지

표현식 satisfies 타입은 표현식이 그 타입에 할당 가능한지 컴파일 시점에 확인한다. 타입 주석(const theme: Theme)도 같은 확인을 하므로, 변수를 그냥 Theme으로 쓸 거라면 타입 주석으로 충분하다. satisfies는 확인은 하되, 작성한 값의 더 구체적인 타입 정보가 필요할 때 유용하다.

type Theme = {
  mode: 'light' | 'dark';
  retryCount: number;
};

const annotated: Theme = { mode: 'dark', retryCount: 3 };
const checked = { mode: 'dark', retryCount: 3 } satisfies Theme;

const a: 'dark' = annotated.mode; // ❌ annotated.mode는 'light' | 'dark'
const b: 'dark' = checked.mode;   // ✅ checked.mode는 'dark'

두 선언 모두 mode: 'blue'나 retryCount: '3'이면 에러가 난다. 차이는 선언 이후다. 타입 주석을 쓰면 변수의 타입이 Theme으로 넓어지고, satisfies를 쓰면 실제 작성한 값에 가까운 타입이 남는다. 다만 모든 속성이 리터럴로 남는 것은 아니다. 위에서 checked.retryCount는 number다.

객체의 키 목록을 강제할 때도 좋다.

type ModuleKey = 'MODULE_1' | 'MODULE_2';

const labels = {
  MODULE_1: '1번 모듈',
  MODULE_2: '2번 모듈',
} satisfies Record<ModuleKey, string>;

MODULE_2를 빼면 키가 없다는 에러가, MODULE_3을 추가하면 정의되지 않은 키라는 에러가 난다. 그러면서도 labels의 타입은 실제 키를 그대로 유지한다.

satisfies는 런타임 검사 도구가 아니다. 서버에서 받은 unknown 값이 User인지 판별하려면 실제 속성을 확인하는 코드(또는 Zod 같은 스키마 검증)가 필요하다.

비교

구분issatisfies
형태함수 반환 타입의 value is Typeexpression satisfies Type
목적검사 결과에 따라 값의 타입 좁히기표현식이 타입 조건에 맞는지 확인
대상함수에 전달한 값직접 작성한 표현식
런타임 동작함수 본문의 검사가 실행됨없음 (컴파일 시점에만)
대표 상황unknown 값 판별, 배열 필터링설정 객체, 상수 맵 구조 확인

요약

  • is는 "검사 함수가 참이면 값을 어떤 타입으로 볼지"를 표현한다.
  • satisfies는 "이 표현식이 요구 타입에 맞는지"를 확인하면서 구체적인 타입을 유지한다.
  • 둘 다 그 문법만으로 외부 데이터의 실제 형태를 검증하지는 않는다. 외부 데이터에는 런타임 검사가 필요하다.

참고