본문 바로가기
WEB/일반

[Typescript] Axios API 함수 리턴하기, 결과값 리턴 차이를 타입의 측면에서 바라보기

by IT황구 2022. 12. 12.
728x90
반응형

Axios를 사용하다보면 API를 호출하는 함수를 통째로 리턴할지, 그 결과값을 리턴할지 고민을 모두 하셨을거라 생각합니다. 개인 프로젝트 진행 중 고민이 됐었는데요.

 

사람마다 다르지만 결과값을 리턴하는 케이스가 많더라구요. 왜 함수를 통째로 리턴하는건 선호되지 않을까요?

 

두개의 차이는 무엇이고 어떤것을 쓰는것이 나은지 알아보겠습니다. (그런 글이 없는것 같아서..ㅎ)

TS를 사용하고 있었기에 차이가 체감 되었습니다.

export default {
// Axios API 함수 리턴
  fetchLectureList : () => {
    return axiosInstance.get(url)
  }
// Axios API 요청의 결과값 리턴
  fetchLectureList : () => {
    return axiosInstance.get(url).then(res => res.data);
  }
}

 

 

차이점 1. Response Type의 차이

당연하지만 두개의 Response Type이 달라야합니다. 어떤 형태를 가져야하고, 왜 그런 형태를 가져야만 하는지에 대해서 알아봤습니다.

바로 이해가 안되더라도 천천히 따라가면, 차이점이 명확히 이해되실 겁니다.

  • 함수 리턴 ( Promise<AxiosResponse<ILectureList>> )

 

fetchLectureList : (url) : Promise<AxiosResponse<ILectureList>> => { 
	return axiosInstance.get(url) 
}

 

 

 

왜 저런 타입으로 되어야 할까요?

 

함수의 형태로 반환한다면 then이나 await을 통해서 값을 받아야 합니다. 예시를 봅시다.

 

fetchLectureList : (url) : Promise<AxiosResponse<ILectureList>> => { 
	return axiosInstance.get(url)
} 

fetchLectureList(url).then(res => res.data);

 

- axios의 method들은 Promise를 반환합니다. 따라서 then을 통해서 반환값에 접근할 수 있습니다.

설명에 앞서 Promise 클래스에 정의된 then 메소드는 어떤것을 인자로 받는지 알아야 합니다.

Promise의 제네릭 타입을 그대로 받아와서, fulfilled case일때 반환할 응답값으로 설정후에, 다시 then에서 리턴하는 값을 Promise로 감싸서 반환합니다.

이것을 보면 우리가 왜 Promise.resolve(1).then(res => res+2).then(res => res+1) 이런식으로 체이닝이 가능했는지 알 수 있습니다.

 

 

parameter 부분은 조금 잘렸습니다.

따라서 Promise<T> 에 존재하는 T에는 AxiosResponse<ILectureList>가 들어갑니다.

그리고 then의 T로 AxiosResponse<ILectureList> 가 전달됩니다.

 

 

AxiosResponse의 들어가는 제네릭 데이터 타입은 data에 들어갈 타입입니다.

그렇다면 fetchLectureList(url) 의 반환값은 위의 형태를 가진 Object를 반환하고, 그 안의 data는 ILectureList 타입을 가졌다. 라는것을 알 수 있습니다. 그렇기 때문에 res.data로 접근이 가능했던 것 입니다.

그렇다면 then(res => res.data) 를 사용해서 반환했다면 어땠을까요?

 

  • 결과값 리턴 ( Promise<ILectureList> )

 

fetchLectureList : (url) : Promise<ILectureList> => { 
	return axiosInstance.get(url).then(res => res.data);
}

 

위의 내용을 쭉 따라왔다면, 결과값을 리턴해줄때는 Promise에 res.data에 들어가는 타입 T만 적어주면 됩니다.

res.data는 결국 then의 반환값에 의해 Promise<res.data> 가 된다는것을 알 수 있습니다.

data는 타입 T였고, T는 ILectureList 이므로, Promise<ILectureList> 가 최종 반환값이 되는 것 입니다.

차이점 2. Promise.All과 destructuring을 조합했을때 Type assertion의 사용 여부

결론만 먼저 짧게 말하면, 함수를 통째로 리턴하면 Promise.All과 destructuring 사용시에 타입 단언을 써야 합니다. (쓰지 않으면 보기 좋지 않습니다.)

예시를 먼저 보여드리겠습니다.

Case 1. 함수를 통째로 리턴한 경우

 

  • map을 사용 할 경우 원소들의 타입들을 유니온으로 묶어서 타입을 만듭니다.
    • const arr = [1,boolean,'str'] // (number | string | boolean)[]
    • map을 사용했기 때문이라기 보다는, 그냥 배열에 값이 동적으로 추가되면 유니온을 붙여서 타입을 추론해야 합니다. (타입스크립트 제작자의 입장에서 생각해보기)

 

  • 이런 단점 때문에, tuple의 형태라고 강제를 해주지 않으면 setState를 할때 union 타입형태를 구체적인 타입에 넣을 수 없다고 나옵니다.

 

  • course는 ICourseDetail임이 틀림 없더라도 union type으로 묶여있어 에러를 내게 됩니다.

 

  • 타입 단언 대신 튜플 하드코딩으로 할 순 있긴 합니다. 근데.. 크흠 map이 가독성이 훨씬 좋고, 원소가 여러개일수도 있기 때문에 좋지 않습니다.

Case 2. 반환값을 리턴 한 경우

  • 반환값을 리턴하면 구조분해 할당이 굉장히 편해집니다. [타입1, 타입2] 의 튜플 형태로 리턴되기 때문입니다.
    • 참고로 Promise.all에 들어가는 인자를 변수로 받아서 넣으면 안됩니다.
    • 그러면 할당시점에 (xx | yy)[]로 추론이 되기 때문입니다.

 


결론

거의 대부분의 케이스에서 응답값을 반환해 주는것이 좀 더 편해보입니다.

하지만 res.data로 데이터만 강제해서 빼주면 AxiosResponse에 있는 headers 같은 속성들을 사용하지 못하게 될 것입니다.

그렇다면 header같은 값들도 반환하려면 어떻게 해야 할 까요?

fetchCourseDetail: (courseId: string): Promise<{data: ICourseDetail,headers: AxiosResponseHeaders }> => {
	return axiosInstance.get(`/courses/${courseId}`).then((res) => ({data: res.data, headers: res.headers}))
}
}); }

이런식으로 객체에 담아서 반환해주거나, res를 그대로 보낼수가 있는데요. 이 경우에는 그냥 함수를 통째로 반환하는것과 다른게 없습니다.

결론을 내는 과정에서 뭔가 많이 얻어간 느낌이 납니다. Promise의 체이닝이 될 수 밖에 없던 이유도 리턴값을 보니 확실히 와닿았습니다.

나이스!

728x90
반응형