Tips/Typescript

[Typescript] 튜플 리터럴을 유니언 타입으로 변환 (feat. 배열의 요소로 이루어진 타입 만들기)

dextto™ 2022. 3. 27. 20:03

타입스크립의 장점이 강력한 타입시스템을 통해 컴파일 타임에 타입 검사를 통해 오류를 검출할 수 있다는 점이다.

가끔 타입을 string, nubmer, Pesron 과 같은 평범한(?) 타입이 아닌 특정 값만을 취할 수 있는 타입을 선언해서 쓸 경우가 있다.

예를 들어, 파일의 확장자를 이미지와 관련된 파일만 받을 수 있다고 해 보자. ext 변수는 'jpg' 또는 'gif' 문자열만 받을 수 있다.

let ext: 'jpg' | 'gif' = 'bmp'; // 구문 오류

이를 별도의 타입으로 선언할 수 있다.

type Ext = 'jpg' | 'gif';
let ext: Ext = 'gif';

나아가 요청으로 받은 데이터의 유효성 검사를 하고 싶다고 하자. 예를 들어 인터페이스를 통해 받은 파일을 표현하는 데이터가 있고 이를 나타내는 FileDto 클래스가 있다.

class File {
  ext: Ext;
}

ext를 위해서 정의한 타입으로 선언했다. 'bmp'문자열을 받았을 때 400 에러를 내도록 처리하도록 하려면 어떻게 해야할까? class-validator를 활용해서 다음처럼 하면 좋겠다.

class File {
  @IsExt()
  ext: Ext;
}

하지만 이 방법은 @IsExt 데커레이터를 따로 구현해야 하는 번거로움이 있다.

튜플 리터럴을 유니언 타입으로 변환한다

먼저 타입스크립트가 제공하는 as const 키워드를 활용해서 허용하는 확장자를 정의한 튜플 리터럴을 선언한다.

const AllowedExt = ['jpg', 'gif'] as const;

AllowedExt의 타입은 string[]이 아니라, readonly ["jpg", "gif"]다. as const가 요소를 추가할 수 없는 튜플로 변환시켜 주었다.
이제 AllowedExt를 이용해서 리터럴 튜플이 가진 요소들만을 가지는 유니언 타입을 선언할 수 있다.

type Ext = typeof AllowedExt[number];

Ext의 타입은 "jpg" | "gif"가 된다. 처음에 선언한 것과 같이 원하는 타입이 만들어졌다!

그리고 이제 유효성 검사를 @IsExt 대신 @IsIn(AllowedExt) 처럼 할 수 있다.

const AllowedExt = ['jpg', 'gif'] as const;
type Ext = typeof AllowedExt[number];

import { IsIn, Validator } from 'class-validator';

class File {
  @IsIn(AllowedExt)
  ext: Ext;

  constructor(ext: Ext) {
    this.ext = ext;
  }
}

const validator = new Validator();
validator.validateSync(new File('gif')); // 유효성 검사 통과
validator.validateSync(new File('bmp' as Ext)); // 수행결과는 ValidationError
반응형