본문 바로가기

FrontEnd

typescript(타입스크립트)의 satisfies 연산자 제대로 알아보기

반응형

TL;DR : satisfies 연산자는 구체적인 데이터를 가장 구체적인 서브타입으로 추론하기 위한 도구다.

Typescript

타입스크립트 4.9에 satisfies 연산자가 도입되었습니다.

공식문서와 여러 블로그에서 해당 개념을 다루고 있지만,

약간 이해하기에 어려운 설명인것 같아 보충합니다.

 

아래 설명은 공식 문서에서 발췌했습니다.

TypeScript 개발자는 종종 딜레마에 직면합니다.
우리는 일부 표현식이 일부 타입과 일치하는지 확인하면서도,
추론을 위해 해당 표현식의 가장 구체적인 타입을 유지하기를 원합니다.

보통 아래와 같이 타입 없이 구체적인 표현식 데이터 종종 선언합니다.

// 각 속성은 스트링이나 RGB 튜플이 될 수 있습니다.
const palette = {
    red: [255, 0, 0],
    green: "#00ff00",
    bleu: [0, 0, 255]
//  ^^^^ 우리는 blue를 의도헀는데, 오타입니다!
};

그리고 아래와 같은 사용 사례에 대해 타입스크립트의 타입 체크 지원을 받고 싶습니다.

// 우리는 '빨간색'에 배열 메서드를 사용할 수 있기를 원합니다...
const redComponent = palette.red.at(0);
// 혹은 'green'의 스트링 메서드를 사용하길 원합니다.
const greenNormalized = palette.green.toUpperCase();

그렇다면 타입을 선언하고 아래와 같이 사용하면 어떨까요?

아래와 같이 오류가 발생합니다.

type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
const palette: Record<Colors, string | RGB> = {
    red: [255, 0, 0],
    green: "#00ff00",
    bleu: [0, 0, 255]
//  ~~~~ 이제 오타는 잘 잡지만
};
/**
Property 'reverse' does not exist on type 'string | RGB'.
Property 'reverse' does not exist on type 'string'.
*/
palette.red.reverse()

참고로 공식문서 예제를 typescript playground에 복붙해도 오류가 발생하지 않습니다.
String에도 at 메서드가 있기 때문입니다.

이러한 오류를 피하려면 아래와 같이 타입 가드를 사용해야 할 것입니다.

const redComponent = typeof palette.red==='object' ? palette.red.reverse() : "";

좋습니다. 지금까지는 공식 문서와 (거의) 동일합니다

 

그렇다면 satisfies는 무엇을 위한 걸까요?

우리는 3번째 코드 예제에서 타입을 먼저 선언하였습니다.

즉 데이터 보다 인터페이스, 타입이 우선하였습니다.

 

하지만 때론 구체적인 데이터가 먼저 존재하고, 해당 데이터를 위한 다형성이 추후에 필요할 경우가 있습니다.

예를 들어 아래 데이터의 경우 우리는 이미 red가 튜플임을 알고 있습니다.

이미 데이터가 먼저 존재하기 때문입니다.

const palette = {
    red: [255, 0, 0],
    green: "#00ff00",
    blue: [0, 0, 255]
};

 

하지만 이전의 3번 예제의 경우 우리는 타입 가드를 통해 이미 알고 있는 사실을 검증해야 했죠.

즉 타입에 의한 데이터의 업캐스팅이 일어난 것입니다.

하지만 우리는 이미 다운캐스팅이 된 상태인 구체적인 데이터 타입을 알고 있습니다.

 

이를 위한 안전한 다운캐스팅 방법이 satisfies 연산자 입니다.

즉, 이미 다운캐스팅 상태나 마찬가지인 데이터에 ,

특정 타입에 대한 안전한 다운캐스팅을 적용하여 해당 서브타입으로 사용하기 위한 방법입니다.

 

이제 이전과 같이 오타도 잘 잡아낼 수 있고,

type RGB = [red: number, green: number, blue: number];
const palette = {
    red: [255, 0, 0],
    green: "#00ff00",
    blue: [0, 0]
    //    ~~~~~~ error!
} satisfies Record<string, string | RGB>;

우리가 아는 한 가장 구체적인 타입처럼 해당 데이터를 사용할 수 있습니다!

type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];

// 서브타입 중 가장 구체적인 타입으로 다운캐스팅 합니다.
/**
{
  red: [number, number, number];
  green: string;
  blue: [number, number, number];
}
 * 
 */
const palette = {
    red: [255, 0, 0],
    green: "#00ff00",
    blue: [0, 0, 255]
} satisfies Record<string, string | RGB>;

// 배열이므로 문제 없음
const redComponent =  palette.red.reverse();

// 에러가 발생하는게 당연함 
const greenComponent =  palette.green.reverse();

간만에 TIL 수준이 아닌 양질의 컨텐츠를 제작한 것 같아 기쁩니다. ^^;

반응형