원티드 프리온보딩 프론트엔드 코스 3번 과제 회고

문성운·2022년 8월 11일
0
post-thumbnail

#3 광고 플랫폼 대시보드

이번 과제는~
광고 플랫폼 대시보드를 만드는 것이다!

🥰 열심히 노력한 8팀의 결과물 🥰
(혹시 모를 보안상의 이유로 생략!)

😎 난 뭘 했나?!

  • react query를 사용한 데이터 통신 구현
    • 통합 광고 현황 불러오기
    • 매체 현황 불러오기
    • 광고 목록 불러오기
    • 광고 추가, 수정 및 삭제

😵 고민... 그리고 또 고민...

과제를 진행하면서 했던 고민들과 상세한 작업 내용을 기록해보자!

caching을 사용하자!

이번 과제에서는 대시보드에서 데이터를 주차별로 불러오거나, 광고의 진행 상태를 구분하여 불러오는 등 데이터를 선택하여 불러오는 경우가 많았다. 풀다운 메뉴를 통해 유저가 선택한 주의 데이터만 불러오는데, 유저가 5주차까지 데이터를 확인한 후 다시 1주차 데이터를 보고 싶어 1주차를 선택했을 경우, 1주차 데이터를 새로 불러올 필요가 있을까? 에 대한 고민을 하게 되었다.

불필요한 request를 막아, 속 시원한 사용자 경험을 제공하기 위해 caching을 사용하고 싶었고 caching을 가장 간단한 방법으로 구현할 수 있는 react query를 사용하기로 하였다.

react query 말고 다른 대안은 없었나?

수많은 서버 상태 관리 라이브러리 중에서 react query를 선택한 이유는, 가장 많이 사용되는 라이브러리였기 때문이다.

가장 많이 사용되기 때문에, 참고할 수 있는 자료가 많을 것이고 참고할 수 있는 자료가 많기 때문에, 다른 라이브러리에 비해 상대적으로 배우기 쉬울 것이라고 판단했다.

1주일 동안 과제를 진행하면서, 본인 작업 외의 다른 팀원들의 코드를 보는 것은 쉬운 일이 아니다. 더욱이 서버 상태 관리 라이브러리를 사용해 본 적이 없는 팀원들이 더 많았기 때문에, 라이브러리 선정은 러닝커브를 최우선으로 고려하였다.

네~ 드셔도 됩니다~

react querykey를 기준으로 cache된 데이터를 관리하기 때문에, 아래 코드와 같이 데이터를 구분하는 string과 시작 날짜(startDate)를 key로 지정하여 관리하였다.

export function useGetDashboard(startDate: string, endDate: string) {
  const query = `?date_gte=${startDate}&date_lte=${endDate}`;

  const overall = useQuery([OVERALL, startDate], () => get(overallService, query));
  const platform = useQuery([PLATFORM, startDate], () => get(platformService, query));

  return [overall, platform];
}

key를 기준으로 데이터가 cache되어 있다고 해서 react query가 데이터를 새로 요청하지 않는 것이 아니다. react query해당 데이터가 신선하지 않다고 판단되면, 서버에 새로운 데이터를 요청한다. 따라서 staleTime을 설정하여, 내가 불러온 데이터는 이 동안은 신선하다! 그러므로 새로운 데이터를 요청할 필요가 없다! 라는 것을 react-query에게 알려주어야 한다.

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60,
    },
  },
});

완성!

첫 선택은 데이터를 요청하지만, 이후부터는 데이터를 새로 요청하지 않는 것을 확인할 수 있다!

변경된 데이터를 불러옵시다!

광고 데이터를 추가, 수정, 삭제하면 변경된 데이터를 새로 불러와야 한다. 위의 첨부한 영상은 데이터 삭제를 위한 mutate 이후 아무런 동작도 하지 않아, 광고 목록이 변경되지 않는 것이다. 특별한 작업 없이, 기존에 관리되던 쿼리(key)를 무효화하는 것으로 해결할 수 있다.

export function useDeleteCampaign(id: number) {
  const query = `/${id}`;
  const { mutateAsync } = useMutation(() => _delete(campaignService, query));

  const queryClient = useQueryClient();

  return async function () {
    await mutateAsync();
    await invalidateQueriesByName(queryClient, CAMPAIGN_CONSTANTS.CAMPAIGN);
  };
}

type typeDataName = typeof OVERALL | typeof PLATFORM | typeof CAMPAIGN;
export function invalidateQueriesByName(queryClient: QueryClient, name: typeDataName) {
  return queryClient.invalidateQueries({
    predicate: (query) => {
      return query.queryKey[0] === name;
    },
  });
}

공식 문서의 invalidateQueries 부분을 읽어보면, staleTime이 만료되지 않은 상태이더라도 무효화한 쿼리를 stale 상태로 변경하고 useQuery hook을 통해 데이터를 새로 불러온다는 것을 알 수 있다. invalidateQueries를 사용함으로써 복잡한 로직 추가 없이 변경된 데이터를 새로 불러올 수 있었다.

우아한 비동기처리 흉내내기!

이번 과제의 대시보드 탭에서는 통합 광고 현황(overall)과 매체 현황(platform) 2개의 데이터를 불러와야 했다. 통합 광고 현황의 경우 지난 주차의 데이터와 비교하여 증감을 표시해야 했기 때문에 총 3개의 비동기 데이터의 상태를 관리해야 했다. 만약 비동기 데이터의 상태를 컴포넌트 내부에서 관리했다면, 코드는 아래와 같았을 것이다.

function DashboardContainer() {
  const [prevOverall] = useGetDashboard(prevStartDate, prevEndDate);
  const [currOverall, currPlatform] = useGetDashboard(currStartDate, currEndDate);

  if (prevOverall.isLoading) {
    return <Loader />;
  }
  if (currOverall.isLoading) {
    return <Loader />;
  }
  if (currPlatform.isLoading) {
    return <Loader />;
  }
  
  return <></>;
}

이러한 문제를 해결할 방법을 찾던 도중에 아래의 영상을 보게 되었다.

https://www.youtube.com/watch?v=FvRtoViujGg
(토스ㅣSLASH 21 - 프론트엔드 웹 서비스에서 우아하게 비동기 처리하기)

함수 본래의 역할 외의 코드들이 많아지면, 함수의 크기가 커지고 복잡도가 높아지기 때문에 함수의 역할이 흐려지고 어떤 함수인지 파악하기 힘들어진다. 따라서 성공하는 경우에만 집중하여 함수의 복잡도를 낮춰 함수가 하는 일을 명확하게 드러낼 수 있도록 해야 한다.

컴포넌트에서 성공한 상태에만 집중할 수 있도록 로딩 상태를 외부에 위임함으로써 동기적인 코드와 큰 차이 없는 코드를 만들어 컴포넌트의 역할을 명확하게 하는 것을 목표로 한다. 이러한 목표 달성을 위해 해당 영상에서는 suspense를 이용하는 방법을 가이드 해 주고 있다.

도전!

// pages/dashboard/index.tsx

<Suspense fallback={<Loader />}>
  <DashboardContainer
    currStartDate={currStartDate}
    currEndDate={currEndDate}
    prevStartDate={prevStartDate}
    prevEndDate={prevEndDate}
  />
</Suspense>
// pages/dashboard/DashboardContainer.tsx

function DashboardContainer() {
  const [{ data: prevOverall }] = getDashboard(prevStartDate, prevEndDate);
  const [{ data: currOverall }, { data: currPlatform }] = getDashboard(currStartDate, currEndDate);

  return <></>;
}

컴포넌트 내부 비동기 데이터의 로딩 상태를 컴포넌트를 쓰는 곳, suspense에 위임하여 우아한 비동기 처리를 흉내 내보았다!

profile
베르나르 성운성운

0개의 댓글