Stouter
Stouter

백엔드 개발자입니다.

Typescript의 함수 반환에 대한 생각

Typscript로 개발을 하다가 함수의 return type과 관련하여 궁금증이 생겨서 멘토분께 질문을 드렸었다. 좋지 않은 질문이었음에도 훌륭한 답변을 해주셔서 그 안에 많은 긍정적인 사고를 할 수 있었다. 새로운 지식을 배웠으니 이를 정리하고 더 깊게 파헤쳐보자.

Q. 함수의 성공 및 실패에 따른 return type

현재 github에 accessToken을 전달하여 유저정보(userInfo)를 받아오는 부분을 함수로 분리하여 사용하고 있는 상황

1
2
//signup.ts
const userInfo = await getUserDataFromGithub(accessToken);

이는 accessToken이 유효한 토큰일 경우 다음과 같은 형식의 userInfo 객체를 반환

1
2
3
4
5
6
interface IUserInfo {
  id: number;
  name: string;
  email: string;
  imgLink?: string;
}

만약 토큰이 유효하지 않은 경우 false나 undefined 등으로 caller에서 결과를 알게끔 해주고 싶었음 그런데 이 후 caller 부분에서 userInfo의 member값을 호출하면 typeScript에서 오류를 띄움(userInfo가 boolean 값이 될 수 있으므로)

1
2
3
// example.ts
const userInfo = await getUserDataFromGithub(accessToken);
console.log(userInfo.name); // 에러

그래서 userInfo의 반환값 type을 일관성있게 만들고자 실패했을 경우 다음과 같은 객체를 반환하도록 고안해봄

1
2
3
4
5
let userInfo: IUserInfo = {
  id: -1,
  name: "",
  email: "",
};

근데 이 또한 좋은 코드는 아닌 것 같아 멘토님께 질문 드렸는데, 아래와 같이 생각할만한 좋은 답변이 왔다.

A. Union Type 사용이 어떨까요?

getUserDataFromGithub 의 반환타입을 좀 더 크게 만들어볼 수 있을거같요! 그리고 그 Response type안에 ErrorResponse & SuccessResponse 를 따로 type을 두는거죠 그리고 error 와 success를 구분하는 필드를 통해 type assert를 해줄 수도 있을거같아요!

1
2
3
4
const response = await getUserDataFromGithub(accessToken);
if(!response.isSuccess){
     throw Error();
}

이런식으로 union type을 사용한 후 각 type을 assert 해줄 수 있는 field를 주시면 -1같은 임의의 값을 사용하지 않을 수 있을거같아요 :)

Documentation - Everyday Types

Union Type을 활용하여 함수 return type 개선

해당 부분을 공부하면서 TypeScript의 신기한 부분을 학습했다. 말 보단 코드로 구현해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
type userDataFailedState = {
  state: false;
};
type userDataSuccessState = {
  state: true;
  userInfo: IUserInfo;
};
type userDataState = userDataFailedState | userDataSuccessState;
// 이 때 까지는 userDataState 라는 타입은 어떤 상태인지 알 수 없다.

/** accessToken 으로 github에서 유저정보 받아오기 */
async function getUserDataFromGithub(
  accessToken: string
): Promise<userDataState> {
  const USER_PROFILE_URL = "https://api.github.com/user";

  try {
    const githubResponse = await axios.get(USER_PROFILE_URL, {
      headers: {
        Authorization: `token ${accessToken}`,
      },
    });

    //성공 시엔 userDataSuccessState 로 userInfo 담기
    const response: userDataSuccessState = {
      state: true,
      userInfo: {
        id: githubResponse.data.id,
        name: githubResponse.data.login,
        email: githubResponse.data.email,
      },
    };

    return response;
  } catch (e) {
    console.log(
      "Error : 해당 유저가 존재하지 않습니다. (getUserDataFromGithub)"
    );
    // 실패시엔 userDataFailedState로 userInfo 를 필요로하지 않는다.
    const response: userDataFailedState = {
      state: false,
    };
    return response;
  }
}

위와 같이 구성하면 좋은점이 무엇이 있을까?? TypeScript는 위와 같은 Union 타입 객체에서 다음과 같은 기능을 제공한다.

1.png

받아올 땐 response 의 타입을 추론하지 못하지만,

if, switch 문으로 특정 field값 혹은 조건을 확정시켜주면, typeScript는 해당 분기내에서 해당 객체의 타입을 정확하게 추론한다.

  • response.state = true 일 때 이므로, userDataSuccessState 로 추론

2.png

  • response.state = false 일 때 이므로, userDataFailedState 로 추론

3.png

따라서 위와 같은 방법으로 해당 함수의 반환값의 타입을 적절히 처리할 수 있다.