시작하는 중

타입스크립트 사용자 정의 타입 가드 본문

타입스크립트

타입스크립트 사용자 정의 타입 가드

싱욱 2023. 4. 26. 16:47

요새 타입스크립트를 배우고 있고 정말 신기한 기능들을 많이 만나고 있어서 왜 이걸 안썼을까~~하는 생각이 자주 든다🙂

 

그런데 타입스크립트는 타입에 대한 강력한 지원을 함에도 불구하고 왜 타입으로 무언가를 할 수 없을까? 하는 생각이 들었다. 예를 들어,

 

function add<T extends number | string>(a: T, b: T): T {
  if (typeof a === 'number' && typeof b === 'number') {
    return (a + b) as T;
  }
  if (typeof a === 'string' && typeof b === 'string') {
    return (a + b) as T;
  }

  throw new Error('number나 string으로 해야해요.');
}

console.log(add(1, 2));     // 3
console.log(add('1', '2')); // 12

 

위의 코드는 사실 vanilla JS로 가능하지만, TS의 제네릭을 활용하여 타입의 안정성을 강화할 수 있다. 이를 통해 개발자는 실행 전에 타입에러를 감지할 수 있다.

 

그런데 타입스크립트를 쓰다보니 궁금한 점이 생겼다.

 

유니온으로 내가 만든 여러개의 타입들을 가질 수도 있는 값에 대해서, 이 값이면 다르게 작동하는 식으로 할 수 있지 않을까

 

이 질문이 글의 논점이다.

 

레츠고


그런데 아쉽게도 내가 만든 타입들은 단순하게 typeof로 적용할 수는 없다. 이유는 타입스크립트는 이를 어떤 타입인지를 알지만, 자바스크립트가 보기에는 단순한 Object에 불과하기 때문이다.

 

예제

interface MadeType {
  vinitus: string;
}

interface A {
  a: string;
  b: MadeType[];
}

const obj1: A = {
  a: '1',
  b: [
    {
      vinitus: '하이',
    },
    {
      vinitus: '헬로',
    },
  ],
};

console.log(typeof obj1.b);      // Object
console.log(obj1.b);             // [...]
console.log(typeof obj1.b[0]);   // Object
console.log(obj1.b[0]);          // vinitus : "하이"

방법을 찾아봤는데 사용자 정의 타입가드라는 것이 있었다. 타입스크립트 딥 다이브

 

사용자 정의 타입 가드 함수란, "단순히 어떤 인자명은 어떠한 타입이다"를 리턴하는 함수일 뿐입니다.

 

라고 하고 있다. 정확하게 내가 원하는 것 !

 

예제

interface MadeType {
  vinitus: string;
}

interface A {
  a: string;
  b: Array<MadeType | string>;
}

const obj1: A = {
  a: '1',
  b: [
    {
      vinitus: '하이',
    },
    '헬로',
  ],
};

function isMadeType(str: any): str is MadeType {
  return str.vinitus !== undefined;
}

function log(obj: A): void {
  Object.values(obj.b).forEach((b) => {
    console.log(b);
    if (isMadeType(b)) console.log('이건 A 타입이에요.', b);
    else console.log('이건 문자열이에요.', b);
  });
}

log(obj1);

 

간단하게 만든 예시이다. 내가 정확하게 원하는 것인 타입에 따라서 다르게 분리처리하는 방법이다.

 

중요한 것은 isMadeType이다. str is MadeType이 중요하다. str을 MadeType이라고 가정하기에 str.vinitus에서 에러가 나지 않는 것이다.

 

any로 쓰면 eslint가 경고를 내주는데, 이 에러를 표시하지 않으려면 다음과 같이 작성해야한다.

function isMadeType(str: MadeType | string): str is MadeType {
  if (typeof str === 'object' && str.vinitus) return true;
  return false;
}

 

이렇게 해야한다. 근데 이러면 사용자 정의 라우터 가드를 통해 편하게 정의하는게 아니라 그냥 자바스크립트 아닌가?? 애매하네...


그래서 하나 더 만들어봤다.

예제

type A = 'A' | 'a';
type WithA = string | A;

function isA(str: string | A): str is A {
  return str === 'A' || str === 'a';
}

function log(arr: WithA[]): void {
  arr.forEach((str) => {
    if (isA(str)) console.log('이건 A 타입이에요.', str);
    else console.log('이건 문자열이에요.', str);
  });
}

log(['A', 'aa', 'b']);

 

이건 좀 더 나은 예제 같다.

 


결론

 

타입스크립트에서 사용자 정의 라우터 가드를 통해서 타입에 따른 분기처리를 할 수 있다.

 

그런데 결국 다른 함수단을 한번 더 거쳐야하는 것인데, 굳이 써야할까? 하는 생각도 든다..

 

 

reference

https://radlohead.gitbook.io/typescript-deep-dive/type-system/typeguard#type-guards