[TypeScript] ReadonlyArray에서 Array.prototype.includes를 타입 오류 없이 쓰는 방법

김기환·2022년 9월 4일
0

문제

ReadonlyArray(const assertions로 생성한 배열)은 각 요소들이 불변하기때문에 그 자체로 타입이 된다.

const colors = ['Slifer Red', 'Ra Yellow', 'Obelisk Blue'] as const;
// 추론되는 타입: readonly ["Slifer Red", "Ra Yellow", "Obelisk Blue"]
type ColorType = typeof colors[number]; // "Slifer Red" | "Ra Yellow" | "Obelisk Blue"
function validateColor(color: ColorType): void {
  // do something
}

위와 같은 방식으로, 상수와 타입을 만드는 패턴을 쓰거나 본 적이 있을 것이다.

그런데 아래와 같은 상황일 때 타입 오류 문제가 발생할 수 있다:

const userInput: string = getUserInput();
function validateColor(color: ColorType): void {
  return colors.includes(userInput); // 타입 에러 !!
}
Argument of type 'string' is not assignable to parameter of type '"Slifer Red" | "Ra Yellow" | "Obelisk Blue"'. ts(2345)

추론된 타입 상 colors의 요소는 Slifer Red, Ra Yellow, Obelisk Blue 중에 하나이다. 따라서 string 타입인 userInput과 비교할 수 없는 것이다.

그렇다면, 아래와 같이 colors와 같은 값을 가지면서 string 타입인 배열을 새로 만들고, includes를 쓰면 문제가 해결될 것이다.

const userInput: string = getUserInput();
const stringColors: string[] = [...colors];
stringColors.includes(userInput); // 정상 !!

하지만 이 해결방법은 다른 문제점을 일으킨다:

const userInput: string = getUserInput();
const stringColors: string[] = [...colors];

function doSomethingWithColor(color: ColorType): void {
	// do something
}

if (stringColors.includes(userInput)) {
  doSomethingWithColor(userInput); // 타입 에러 !!
}
Argument of type 'string' is not assignable to parameter of type '"red" | "green" | "blue"'.ts(2345)

inlcudes를 통과하더라도, userInput은 여전히 string으로 추론된다. stringColors의 타입이 string[]이기 때문에 타입이 좁혀지지 않는 것이다.

해결법

타입 서술어 (type predicates)를 사용하면 된다.
boolean을 반환하는 함수에서 사용할 수 있는데, 함수의 파라미터의 타입을 좁혀준다.

export function contains<T extends string>(
  list: ReadonlyArray<T>,
  value: string,
): value is T {
  return list.some((item) => item === value);
}

위 함수가 true를 반환하면, 타입 체커가 value 파라미터를 T 타입으로 추론한다는 의미이다.
T는 generic이기 때문에 contains(colors, userInput) 실행 컨텍스트에서는 ColorType로 추론된다.

const userInput: string = getUserInput();
if (contains(colors, userInput)) {
  doSomethingWithColor(userInput); // 정상 !! if 문을 통과하면서 userInput이 ColorType 추론된다.
}

한계

타입 서술어로 타입을 넓힐 수는 없다. 그런 상황일 땐 별 수 없이 타입 단언문 as를 써야만 한다.

참고자료

profile
Front-end dev · Human-Computer Interaction

0개의 댓글