재즈밋 프로젝트를 진행하며 생겼던 타입스크립트의 문제 상황을 기록하고 공유하는 글입니다.
더 좋은 해결방법이나 잘못된 정보가 있다면 댓글로 알려주세요.
이미지 업로드 요청을 구현하는 중 확장자를 제한하는 로직을 짜고 있었습니다. 여기서 png, jpg, jpeg의 확장자만 허용하도록 정했고, 이를 따로 readonly array로 관리하였습니다. 그리고 Array.includes 메서드로 확장자를 검사하면서 문제가 생겼습니다.
const ALLOWED_EXTENSIONS = ['png', 'jpg', 'jpeg'] as const; // const ALLOWED_EXTENSIONS: readonly ["png", "jpg", "jpeg"]
ALLOWED_EXTENSIONS를 const assertions으로 상수화하였고,
function uploadImage(image: File) {
const extension = image.split('.').pop();
if(extension && ALLOWED_EXTENSIONS.includes(extension)) { // Argument of type 'string' is not assignable to parameter of type '"png" | "jpg" | "jpeg"'.ts(2345)
// ^^^^^^^^^
}
// ...
}
Argument of type 'string' is not assignable to parameter of type '"png" | "jpg" | "jpeg"'.ts(2345)
에러 내용은 "string 타입은 type '"png" | "jpg" | "jpeg"'에 할당할 수 없다"고 나옵니다.
왜 이런 에러가 나오게 되었을까 살펴보니

위와 같이 타입스크립트는 Array, ReadonlyArray의 includes 메서드를 정의할 때 첫번째 인자를 Array의 제네릭을 따르도록 정의되어 있습니다.
ALLOWED_EXTENSIONS는 "png" | "jpg" | "jpeg"의 유니온 타입이었고, 첫 번째 인자인 extension은 string이었기 때문에 에러가 났던 것입니다.
해결 방법은 매우 다양하지만 몇가지만 다루겠습니다.
const assertions가 아닌 Type Annotation을 활용하여 ALLOWED_EXTENSIONS을 ReadonlyArray<string>으로 상수화를 할 수 있습니다.
const ALLOWED_EXTENSIONS: Readonly<string> = ['png', 'jpg', 'jpeg'];
includes 앞에 배열 제네릭 타입이 string이 되고 extension은 string이므로 에러가 나지 않습니다.
Type Predicates을 활용하여 extension의 타입을 좁혀줍니다.
function isInArray<T>(array: ReadonlyArray<T>, value: unknown): value is T {
return array.includes(value as T);
}
extenstion의 타입을 assertion합니다.
if(extension && ALLOWED_EXTENSIONS.includes(extension as typeof ALLOWED_EXTENSIONS[number])) {
includes 인터페이스 재정의includes를 사용하지 않고 some 메서드로 값 확인위 방법 모두 100% 해결했다고 볼 수 없을 것 같습니다.
개중에 1번 방법이 적절하다고 생각됩니다. 배열의 읽기 전용 타입을 지킬 수 있고 추가 로직이 없으며 includes의 인터페이스를 그대로 유지할 수 있기 때문입니다.