본문 바로가기

Study Output for Myself/TypeScript

[TS] type narrowing, discriminated union

타입스크립트 공식문서 :https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions

 

Documentation - Narrowing

Understand how TypeScript uses JavaScript knowledge to reduce the amount of type syntax in your projects.

www.typescriptlang.org


서버에 api 요청을 보냈다.

요청이 별 문제 없이 성공할 경우 내가 원하는 데이터가 넘어올 것이고 요청이 실패한다면 넘어오지 않을 것이다. 

이 때 에러처리를 위해서 요청이 실패할 경우, 각 경우에 맞는 에러객체를 만들어서 반환하도록 설계해보자. 

그러면 이제 api 요청을 보냈을 때 넘겨 받을 수 있는 데이터는 2가지 중 하나이다.

 

1. 요청한 데이터 객체 (ApiMovie)

2. 에러 객체 (ApiError)

private async getApiData(){
    const url = this.makeUrl();
    const apiData = await request(url);
    return apiData;
}

타입스크립트를 활용해 apiData가 데이터객체이거나 에러 객체이라는 것을 명시해주고 싶다.

둘 중 하나일테고 다른 데이터객체가 반환될수는 없으니까! 

타입을 명시해준다면 데이터를 받아서 쓰는 쪽에서 프로퍼티를 안전하게 사용할 수 있는 장점을 활용하고 싶다.

private async getApiData():Promise< ApiMovie | ApiError >{
    const url = this.makeUrl();
    const apiData = await request(url);
    return apiData;
}

오 좋은데? 

그런데 반환타입을 명시해주자마자 데이터를 받아서 사용하는 메소드에 빨간 줄이 그어지기 시작했다. 

  async getMovies() {
    const apiData = await this.getApiData();

    if (apiData.error) {
     console.log(`${apiData.errorMessage}로 인한 에러가 발생했습니다.`);
     return;
    }

    const desiredData = apiData.results;
    console.log(`원하던 데이터입니다.`);
  }

apiData는 apiMovie이거나 apiError 인데

apiMovie에는 error 속성이 없고, apiError에는 results 속성이 없다고 타입스크립트가 불만을 표시하는 거다!

왜냐면 메소드 입장에서는 apiData가 apiMovie인지 apiError인지 알 수 없으니까. 

 

Generic으로 해결해줘볼까? 

//Generic 사용 해결 예시

  private async getApiData<T>(): Promise<Awaited<T>> {
    const url = this.makeUrl();
    const apiData = await request(url);
    return apiData;
  }

 에러는 없어졌지만 apiMovie와 apiError에 있는 속성을 보장받으면서 안전하게 사용하고 싶다.

Generic은 정말 아무 객체나 반환할 수 있는 거 잖아 ㅠㅠ

 

이 때 타입스크립트의 Discriminated Union이라는 스펙을 활용해보자. 

반환받을 수 있는 두 개의 객체에 type(네이밍은 달라도 된다. 같은 네임이기만 하면 됨!)이라는 속성을 명시적으로 넣어주고 type의 값을 활용해 반환받은 객체가 두 가지 중 어떤 객체인지 확인 후 사용할 수 있게 하는 것이다.

interface ApiMovie {
  page: number;
  total_pages: number;
  results: ApiMovieItem[];
  total_results: number;
}

interface ApiError {
  error: number;
  errorMessage: string;
}

이런 식으로 존재하던 인터페이스를 

interface ApiMovie {
  type:'fulfilled';
  page: number;
  total_pages: number;
  results: ApiMovieItem[];
  total_results: number;
}

interface ApiError {
  type:'rejected';
  error: number;
  errorMessage: string;
}

type 속성을 추가해줬다.

 

이제 데이터를 받아오는 쪽 메소드를 수정해주자.

  async getMovies() {
    const apiData = await this.getApiData();

    if (apiData.type === 'rejected') {
     console.log(`${apiData.errorMessage}로 인한 에러가 발생했습니다.`);
     return;
    }

    const desiredData = apiData.results;
    console.log(`원하던 데이터입니다.`);
  }

type을 확인시킨 후 다뤄주니 더 이상 에러를 발생시키지 않는다! 얄루~~

객체 안에 있는 속성도 자동완성으로 떠서 안전하게 오타걱정이나 없는 속성을 불러올 걱정없이 사용할 수 있게 되었다! 

'Study Output for Myself > TypeScript' 카테고리의 다른 글

[Typescript] Utility Types  (0) 2023.04.28
[TS] Generic(제네릭)  (0) 2022.07.05
[TS]속성접근자  (0) 2022.07.04
[TS]인터페이스(interface)  (0) 2022.07.02
[TS]타입 알리아스(type alias)  (0) 2022.07.02