TypeScript 팁: 조건부 유형을 사용하여 오버로드를 리팩터링합니다.

소개



며칠 전에 코드를 리팩토링하다가 다음과 같은 것을 발견했습니다.

interface IRawUser {
  first_name: string
  email: string,
  id: number,
}

interface User {
  name: string,
  email: string
  print: () => void
}

declare function userFactory(rawUser: IRawUser): User;

function mapRawToUserObject(rawShow: IRawUser[]): User[];
function mapRawToUserObject(rawShow: IRawUser): User;
function mapRawToUserObject(rawShow: IRawUser | IRawUser[]): User | User[] {
  if (rawShow instanceof Array) {
    return rawShow.map((raw) => userFactory(raw));
  }

  return userFactory(rawShow);
}


함수mapRawToUserObject는 오버로드를 사용하여 다음을 표현합니다. IRawUser[]의 배열로 호출하면 User[]의 배열을 반환하고, 단일IRawUser로 호출하면 단일User을 반환합니다. ) .

너무 복잡하지는 않지만 메서드 정의를 리팩토링할 수 있는 좋은 기회인 것 같습니다.

조건부 유형 생성.



메서드가 받는 입력 유형을 결정해야 합니다.

살펴봐야 할 가능한 입력은 IRawUserIRawUser[]입니다.

type MapRawResult<T extends IRawUser | IRawUser[]> = any;


그런 다음 조건부 논리를 추가합니다.

type MapRawResult<T extends IRawUser | IRawUser[]> = T extends IRawUser ? User : User[]


In plain words: If T is assignable to IRawUser return User otherwise return User[]





우리의 방법에 조건부 유형을 적용합니다.



지금 우리의 방법은 다음과 같습니다.

function mapRawToUserObject(rawShow: IRawUser[]): User[];
function mapRawToUserObject(rawShow: IRawUser): User;
function mapRawToUserObject(rawShow: IRawUser | IRawUser[]): User | User[] {
  // implementation
}


이제 오버로드를 제거하고 일반 매개변수( T extends IRawUser | IRawUser[] )를 추가하고 반환 유형을 최근에 만든 것으로 바꿀 수 있습니다.

type MapRawResult<T extends IRawUser | IRawUser[]> = T extends IRawUser ? User : User[]

function mapRawToUserObject<T extends IRawUser | IRawUser[]>(rawShow: T): MapRawResult<T> {
  // implementation
}


이 시점에서 typescript는 우리가 반환하는 값의 유형에 대해 불평할 것입니다.



컴파일러는 우리가 반환하는 값의 유형을 유추할 수 없습니다. 이에 대한 해결책은 키워드 as 를 사용하여 예상되는 유형을 명시적으로 표현하는 것입니다.

최종 방법은 다음과 같습니다.

function mapRawToUserObject<T extends IRawUser | IRawUser[]>(rawShow: T): MapRawResult<T> {
  if (rawShow instanceof Array) {
    return rawShow.map((raw) => userFactory(raw)) as MapRawResult<T>
  }

  return userFactory(rawShow) as MapRawResult<T>
}


결론



일부 값으로 시도하면 메서드가 오버로드를 사용하지 않고 이전과 동일하게 동작하는 방식을 볼 수 있습니다.





보너스로 IRawUser | IRawUser[]에 대한 유형 별칭을 사용하고 코드를 더 깔끔하게 만들 수 있습니다.

type MapRawArg = IRawUser | IRawUser[];

type MapRawResult<T extends MapRawArg> = T extends IRawUser ? User : User[];
function mapRawToUserObject<T extends MapRawArg>(rawShow: T): MapRawResult<T> {
  // ...
}


추가 자료


  • Conditional Types
  • Type Assertions
  • Generics
  • TypeScript Playground
  • 좋은 웹페이지 즐겨찾기