프로젝트에서 북마크된 영화 목록 기능을 구현하는 과정에서 고민하고 최종적으로 결정한 사항을 공유합니다. 아래는 초기 코드와 최종 코드, 그리고 각 코드의 장단점을 비교한 개선된 사항입니다.
고민 이유
- 북마크된 영화 목록 기능:
- 개별 영화의 상세 페이지에서 북마크를 추가하고, 이를 모아 북마크된 영화 목록을 표시하는 기능.
- 북마크된 영화 목록을 표시하기 위해 각 영화의 세부 정보를 요청할 때 서버 과부하 우려.
- 데이터 요청에 대한 고민:
- 인기순, 최신순 등의 영화 목록에서 중복된 영화가 있음.
- 실제로 필요한 데이터보다 많은 데이터를 한 번에 요청하게 되어 불필요한 데이터까지 받아오게 됨.
해결 방안
- 한 번에 요청하는 방법:
- 장점:
- 네트워크 효율성: 여러 개의 요청을 하나로 줄여 네트워크 부하를 줄임.
- 응답 속도: 한 번의 요청으로 필요한 모든 데이터를 받아옴.
- 코드 간결화: 요청/응답 패턴이 단순화되어 코드가 간결해짐.
- 단점:
- 필요 이상으로 데이터를 요청: 실제로 필요한 데이터보다 많은 데이터를 한 번에 요청하게 되어 불필요한 데이터까지 받아오게 됨.
- 복잡한 백엔드 구현: 여러 데이터를 한 번에 받아오기 위한 추가적인 백엔드 API 구현 필요.
- 장점:
- 필요한 데이터만 여러 번 요청하는 방법:
- 장점:
- 데이터 일치 보장: 각 요청마다 최신 데이터를 받아옴.
- 세분화된 제어: 필요한 데이터만 요청하여 효율적.
- 기존 API 활용: 추가적인 백엔드 구현 없이 현재 API를 그대로 사용할 수 있음.
- 단점:
- 네트워크 부하: 여러 개의 요청이 네트워크 부하를 증가시킬 수 있음.
- 클라이언트 코드 복잡성: 많은 요청을 처리하는 코드가 복잡해질 수 있음.
- 장점:
- 캐싱 사용:
- 장점:
- 데이터 무결성 유지: 캐싱을 통해 중복 요청을 방지하고 최신 데이터를 유지.
- 성능 최적화: 불필요한 네트워크 요청을 줄여 성능 향상.
- 단점:
- 캐싱 관리 필요: 데이터의 최신 상태를 유지하기 위한 캐싱 전략 필요.
- 장점:
최종 선택 이유
- 북마크 목록이 많지 않을 것으로 예상.
- 코드 복잡성 해결: 커스텀 훅을 사용해 복잡한 부분 처리.
- 병렬 비동기 요청 최적화: Promise.all → useQueries를 사용하여 응답 시간을 최적화.
- 데이터 일치 보장: 각 영화 ID가 일치할 것을 보장.
- 캐싱 추가: 데이터 캐싱을 통해 중복 요청 방지 및 성능 최적화.
초기코드
// 북마크 영화 목록 불러오기
export const useGetBookmarksWithDetails = (userId: string) => {
return useQuery({
queryKey: ["bookmarksWithDetails", userId],
queryFn: async () => {
const bookmarks: BookmarkRow[] = await api.bookmark.getBookmarks(userId);
const movieDetails = await Promise.all(
bookmarks.map(async (bookmark) => {
const movie = await api.movie.fetchMovieDetails(
Number(bookmark.movie_id)
);
return movie;
})
);
return movieDetails;
},
});
};
최종코드
// 북마크 영화 목록 불러오기
export const useGetBookmarksWithDetails = (userId: string) => {
const queryClient = useQueryClient();
const bookmarksQuery = useQuery({
queryKey: ["bookmarks", userId],
queryFn: () => api.bookmark.getBookmarks(userId),
enabled: !!userId,
});
const movieQueries = useQueries({
queries: bookmarksQuery.data
? bookmarksQuery.data.map((bookmark) => {
const movieId = Number(bookmark.movie_id);
return {
queryKey: ["Movie", movieId],
queryFn: () => api.movie.fetchMovieDetails(movieId),
enabled: bookmarksQuery.isSuccess,
};
})
: [],
});
useEffect(() => {
if (bookmarksQuery.data) {
queryClient.invalidateQueries({ queryKey: ["Movie"] });
}
}, [bookmarksQuery.data, queryClient]);
const movieDetails = movieQueries
.map((result) => result.data)
.filter((movie): movie is Movie => movie !== undefined);
// 전체 상태를 통합하여 하나의 상태로 반환
const status: "pending" | "error" | "success" =
bookmarksQuery.status === "pending" ||
movieQueries.some((result) => result.status === "pending")
? "pending"
: movieQueries.some((result) => result.status === "error")
? "error"
: "success";
return {
data: movieDetails,
status,
};
};
개선된 사항
- 구조적 개선:
- 초기 코드: 모든 북마크된 영화의 세부 정보를 한 번에 가져오는 방식으로, 네트워크 부하가 크고 비효율적일 수 있음.
- 최종 코드: useQueries를 사용하여 각 영화를 병렬로 가져와 비동기 처리를 최적화하고, 개별 요청 실패 시에도 다른 요청에 영향이 없도록 개선.
- 비동기 처리 최적화:
- 초기 코드: Promise.all을 사용하여 동시 요청을 처리하지만, 이는 네트워크 부하를 줄이기에는 한계가 있음.
- 최종 코드: useQueries를 통해 각 요청을 병렬로 처리하여 응답 시간을 최적화하고, React Query의 장점을 활용.
- 캐싱:
- 최종 코드: queryClient.invalidateQueries를 사용하여 캐시를 효율적으로 관리함으로써 불필요한 네트워크 요청을 줄이고 성능을 최적화.
- 상태 관리:
- 초기 코드: 단일 상태로 모든 데이터를 관리.
- 최종 코드: 북마크 상태와 영화 세부 정보 상태를 통합하여 전체 상태를 세밀하게 관리, 더 견고한 코드 작성 가능.
- 확장성:
- 최종 코드: 각 요청을 개별적으로 처리하고 상태를 통합하여 관리함으로써, 새로운 기능 추가나 다른 API와의 통합이 용이.
'TIL' 카테고리의 다른 글
TIL - 무한 스크롤 데이터 요청 중 깜빡임 제거 (0) | 2024.07.04 |
---|---|
TIL - 재사용 가능한 컴포넌트 만들기: class-variance-authority 활용 (0) | 2024.07.03 |
TIL - Next.js&supabase에서 createClient Vs. createServerComponentClient (0) | 2024.07.01 |
TIL - TanStack Query 무한 스크롤 커스텀 훅 리팩토링 (0) | 2024.06.29 |
TIL - Supabase에서 .select() 사용하기 (0) | 2024.06.28 |