매개변수 기반 Typescript 함수 반환 유형

Typescript는 런타임 값을 기반으로 하는 일부 동작을 원할 때까지 모두 재미있고 게임입니다. 최근에 까다로운 문제가 발생했습니다. 매개변수 값을 기반으로 함수의 반환 유형을 어떻게 입력합니까?

나는 이 소리가 반패턴이라는 것을 알고 있지만 이에 대한 실제 사용 사례가 많이 있습니다. 예를 들어 함수에 반환하는 값의 유형을 결정하는 option 필드가 있습니다.

type User = {
  id: string;
  firstName: string;
  lastName: string;
  profilePicture?: string | ProfilePicture;
};
type ProfilePicture = {
  height: string;
  width: string;
  url: string;
};

const db = {
  findUserById: async (userId: string): Promise<User> => ({
    id: '1',
    firstName: 'Bruce',
    lastName: 'Wayne',
  }),
};

const generateProfilePictureById = async (
  userId: string
): Promise<ProfilePicture> => ({
  height: '20px',
  width: '20px',
  url: `http://example.com/${userId}.png`,
});

const getUserProfile = async (
  userId: string,
  options?: { generateProfilePicture: boolean }
) => {
  const user = await db.findUserById(userId);
  if (options?.generateProfilePicture) {
    return {
      ...user,
      profilePicture: await generateProfilePictureById(userId),
    };
  }
  return { ...user, profilePicture: 'picture not generated' };
};


이제 다음과 같이 getUserProfile를 사용하려는 경우:

(async () => {
  const user = await getUserProfile('1', { generateProfilePicture: true });
  console.log(
    `${user.firstName} ${user.lastName} has profilePicture with height: 
    ${user.profilePicture.height}`
  );
})();


Typescript는 heightuser.profilePicture에 존재하지 않는다고 불평합니다.

그러나 generateProfilePicture 옵션이 true 로 설정되어 있으면 user.profilePicture가 유추된 유형string | ProfilePicture이 아님을 알고 있습니다.

그러면 이 문제를 어떻게 해결할 수 있습니까? Typescript에 답이 있습니다: Function overload
기본적으로 typescript는 코드에 나타나는 순서대로 함수의 여러 서명을 매핑합니다. 해당 함수에 대해 일치하는 첫 번째 유형 서명을 사용합니다.

이것을 알고, 우리 함수의 타이핑을 개선합시다getUserProfile:

interface GetUserProfileType {
  <T extends boolean>(
    userId: string,
    options?: { generateProfilePicture: T }
  ): Promise<
    Omit<User, 'profilePicture'> & {
      profilePicture: T extends true ? ProfilePicture : string;
    }
  >;
  (
    userId: string,
    options?: { generateProfilePicture: boolean }
  ): Promise<User>;
}
const getUserProfile: GetUserProfileType = async (
  userId: string,
  options?: { generateProfilePicture: boolean }
) => {
  const user = await db.findUserById(userId);
  if (options?.generateProfilePicture) {
    return {
      ...user,
      profilePicture: await generateProfilePictureById(userId),
    };
  }
  return { ...user, profilePicture: 'picture not generated' };
};


이제 user.profilePicturestring 일 때 generateProfilePicturefalse 가 되고 ProfilePicturegenerateProfilePicture 일 때 true 가 됩니다.


하지만 잠깐만 더 있어요
options를 완전히 생략하고 다음과 같이 사용하면 어떻게 될까요?

(async () => {
  const user = await getUserProfile('1');
  console.log(
    `${user.firstName} ${user.lastName} has profilePicture with height: 
    ${user.profilePicture.length}`
  );
})();


이제 위의 코드에 대해 typescript는 Property 'length' does not exist on type 'ProfilePicture' 를 불평합니다. 분명히 두 함수 오버로드 중 어느 것과도 일치하지 않았습니다. 글쎄, 세 번은 매력이라고 생각하고 세 번째 함수 오버로드를 추가해 보겠습니다.

interface GetUserProfileType {
  <T extends { generateProfilePicture: boolean } | undefined>(
    userId: string,
    options?: T
  ): Promise<
    Omit<User, 'profilePicture'> & {
      profilePicture: T extends undefined ? string : never;
    }
  >;
  <T extends boolean>(
    userId: string,
    options?: { generateProfilePicture: T }
  ): Promise<
    Omit<User, 'profilePicture'> & {
      profilePicture: T extends true ? ProfilePicture : string;
    }
  >;
  (
    userId: string,
    options?: { generateProfilePicture: boolean }
  ): Promise<User>;
}


이제 코드가 예상대로 작동합니다.

좋은 웹페이지 즐겨찾기