[WANTED] ASSIGNMENT_6

jun gwon·2022년 11월 30일
0

원티드 프리온보딩

목록 보기
13/14

ASSIGNMENT_5

여섯번째 과제의 요구사항

과제의 대략적인 요구사항은 아래와 같았습니다.

  1. 대략적인 Layout만 잡혀있고, 페이지 디자인은 자율적 구현
  2. Page는 총 3개로, 로그인 / 계좌리스트 Page / 계좌 상세 페이지
  3. 계좌 리스트 Page는 Token값이 있을때만 접근가능하고, 페이지네이션이 될것
  4. 계좌 리스트 Page는 옵션을 통해 검색을 비롯해 필터링이 가능할것
  5. 계좌 상세 정보 페이지는 수정이 가능할것
  6. 브로커명을 비롯해 여러 데이터들은 json 파일을 참조하여 데이터에 맞게 문자를 변환하고, 계좌 번호는 앞위 각 2글자씩을 제외하고 * 처리할것
  • 이번 과제는 과제기한이 5.5일로 기존 과제보다 조금 더 길게 시간이 주어졌습니다.
    ++ 이번 과제에서는 TS와 NextJS, ReactQuery가 권장되었기때문에, 해당 기술을 사용하였습니다.
    +++ API 서버는 별도로 주어지지 않으며, json 파일만을 가진 repo를 json 서버로 구동하여 로컬환경에서 작업할것

시작하기에 앞서

초기 셋팅

초기 셋팅은 이전 과제물들과 유사하게 진행하였습니다.
팀원 한분이 불필요한 파일을 제거하고, ESLint,prettier,husky 설정이 된 repo를 생성하고 main브런치에 올린뒤, 팀원들이 각자 클론하여 브런치를 생성하여 각자의 브런치에서 진행하였습니다.

코딩구현

1. 공통 레이아웃 구현 및 애니메이션 구현

제가 구현한 레이아웃은 총 4부분으로 되어있습니다. 헤더 / 메인(contents) / 푸터 /사이드 메뉴 Bar

Layout 파일을 만들고 그곳에 저 4개의 컴포넌트를 지정하고, 메인은 react와 유사하게 children을 통해 공용으로 사용하였습니다. next는 outlet이 없기 때문에 약간의 사용방법을 익히는데 시간이 들긴 하였습니다.

하지만 그것보다는 사이드바 의 애니메이션을 구현한게 마음에 들었던것 같습니다.

전체적인 구상은 사이드 바를 on/off 할 수 있고, 그 때마다 사이드 바는 translate를 통해 움직이고 동시에 헤더,메인,푸터도 같이 움직이는 형식입니다.

초기에는 각각의 컴포넌트마다 absolute를 주고, 각각 애니메이션을 지정해주었지만, 생각해보니 그냥 wrapper를 2개를 만들어서 그 두개에만 이벤트를 주면 되겠구나 하는 생각이 들어 리팩토링을 하게 되었습니다.
(구현은 아래와 같습니다.)

    <LayoutWraaper>
      <SlideBarWrapper show={show}>
        <SideBar />
      </SlideBarWrapper>

      <MainWrapper show={show}>
        <Header setShow={setShow} show={show} />
        <ContentsWrapper>{children}</ContentsWrapper>
        <Footer />
      </MainWrapper>
    </LayoutWraaper>

가장 상위의 Layout에 relative를 주었고, SlideBarWrapper와 MainWrapper에 각각 absolute를 주고, 애니메이션은 아래와 같이 구현하였습니다.

const SlideBarWrapper = styled.div(({ show }: { show: boolean }) => [
  show ? tw`translate-x-0 ` : tw`-translate-x-full`,
  ...
  ]
  const MainWrapper = styled.div(({ show }: { show: boolean }) => [
  show ? tw`translate-x-[256px] ` : tw`translate-x-0`,
  ...
  ]

Slidebar가 on됐을시 그 만큼만을 Main에서 밀어주면 되고,Off시는 제자리로 오게 하면 되는 간단한 transform 처리이지만, 많이 다뤄보지 않았던 애니메이션이기도 하였고, 기존에는 그저 인터넷 소스를 가져와 사용하는 방식이였지만, 이번에는 직접 애니메이션을 어떻게 구현할지 구상하고 구현해본게 마음에 들었던것 같습니다.

2. 페이지 네이션

페이지네이션 자체도 사실 구현이 어려운편은 아니지만, 역시 기존에는 다른 Lib등을 사용하다 직접 구현해보려니 이게 생각만큼 구현이 잘 되진 않아서 약간 헤맸던것 같습니다.
(구현은 아래와 같습니다.)

const VIEW_PAGE_COUNT = 5;
const LIMIT = 10;

export function pagination(count: number) {
  let totalCount = Math.ceil(count / LIMIT);

  let originCntArr = [];
  let resultArr = [];
  for (let index = 1; index <= totalCount; index++) {
    originCntArr.push(index);
  }
  let totalPage = Math.ceil(originCntArr.length / VIEW_PAGE_COUNT);

  for (let index = 0; index < totalPage; index++) {
    let spliceArr = originCntArr.splice(0, 5);
    resultArr[index] = spliceArr;
  }
  return resultArr;
}

count는 총 데이터 배열의 값입니다. LIMIT는 한 페이지에서 보여줄 데이터의 양이고, VIEW_PAGE_COUNT는 한번에 보여줄 버튼의 갯수입니다.

구현에 있어 아쉬운건 for문을 두번 돌린건데, 돌이켜보니 for문에서 index=totalPgae를 주고, index-(VIEW_PAGE_COUNT)를 했다면 for문을 한번만 돌리고 완성 할 수 있엇을텐데 구현이 좀 아쉬운것 같습니다.

추가적으로, 버튼 이동시 데이터가 패칭되고, 활성화 버튼값이 유지되어야 하기 때문에, 아래와 그 부분은 아래와 같이 구현하였습니다.

  const page = router.query.page ? parseInt(router.query.page as string, 10) : 1;
  const defaultOffset = Math.ceil(page / 5) - 1;
  const [offSet, setOffSet] = useState(0);
  
    useEffect(() => {
    setOffSet(defaultOffset);
  }, [defaultOffset]);

(offset으로 버튼이 있어야 하는 위치를 나타내었습니다. 1~5 or 6~10 등..)
(useState의 기본값으로 defaultOffset을 주면 초기값이 제대로 잡히지 않아, useEffect를 사용하여 offset값을 설정하였습니다.)

  const paginationRender = (pagiArr: number[]) => {
    return pagiArr.map((pageNumber: number, idx: number) => (
      <PaginationLi
        isActive={pageNumber === page}
        onClick={() => router.push(`?page=${pageNumber}`)}
        key={`${pageNumber}_${idx}`}
      >
        {pageNumber}
      </PaginationLi>
    ));
  };

페이지네이션 렌더부분, router를 통해 url의 page를 읽어오고 렌더되는 컴포넌트의 pageNumber와 page가 동일하면 active효과를 주었습니다.

3. react-query를 사용한 구현

이번 프로젝트에서 react-query는 단순 데이터 패칭 뿐 아니라, SSR에서도 사용하였습니다.
react-query에서 관리하는 queryClient의 dehydrateState를 사용하는방식인데, 사용하기위해 _app.tsx에서 Hydrate 설정을 하였습니다.

SSR을 사용한 부분은 AccountList. 계좌목록에서 사용하였습니다.

export const getServerSideProps: GetServerSideProps = async context => {
  const queryClient = new QueryClient();
  const page = context.query.page ? parseInt(context.query.page as string, 10) : 1;
  await Promise.all([
    queryClient.prefetchQuery(['userList'], userAPI.getUser),
    queryClient.prefetchQuery(['AccountList', 1], () => accountAPI.getList(page)),
  ]);
  return {
    props: {
      dehydratedState: dehydrate(queryClient),
    },
  };
};

(구현부분)
페이지 접근을 단순히 컴포넌트를 통한 이동뿐만 아니라 URL을 통한 이동으로도 할 수 있기 때문에 context를 통해 query의 page param을 가져와 사용하거나, 기본값을 사용하였습니다.

prefetchQuery를 사용하여 서버단에서 데이터를 읽어오고, Promise.all을 사용하여 두 데이터 모두 정상적으로 처리 되었을경우에만 정상값을 리턴시키도록 하였습니다.

그 외 값이 SPA에서 데이터를 패칭하는 useQuery사용은 아래와 같이 단순하게 사용하였습니다.

export const useAccountList = (page: number) => {
  const queryData = useQueries({
    queries: [
      { queryKey: ['userList'], queryFn: userAPI.getUser, staleTime: 1000 * 60 },
      {
        queryKey: ['AccountList', page],
        queryFn: () => accountAPI.getList(page),
        staleTime: 1000 * 60,
      },
    ],
  });
  const [userData, accountData] = queryData;

  return [userData, accountData];
};

(페이지가 변할시 새로운 계좌 데이타를 가져오는 부분)
staleTime은 _app.tsx에서 한번에 지정할 수도 있지만, 각 데이터 마다 stale타임을 명시해주는것도 좋다 생각하여 따로 적어주었습니다.

좋았던 부분 및 개선된 부분

  1. tailwind-styled-components는 TS에서 결점이 있어서, 이전부터 사용해보고 싶었던 twin.macro를 사용하여 스타일링적인 구현부분은 상당부분 개선된게 느껴졌습니다.
  2. 주 스택이 아니였던 NextJS, React-query를 사용하여 결과물을 냈단점이 좋았습니다
  3. Transform 애니메이션을 어떻게 사용해야 하는지에 대해서 전체적인 감을 잡을 수 있었습니다.
  4. NextJS에서 SSR 및 SSG를 어떻게 사용하는지, 그리고 CORS는 어떻게 방지하는지 등. Next를 실전에서 어떻게 사용하는지에 대한 경험을 하게되었습니다.
  5. NextJS의 ServerSideProps가 아닌, React-query의 Hydrate state를 통한 SSR을 알게됐고, 구현까지 해보게 되어 좋았습니다.
  6. 단순히 SPA적인 접근으로 데이터를 받아오는게 아닌, URL로 특정 데이터로도 접근 가능하게 구현한게 좋았습니다.
  7. 전체적으로 이번 프로젝트에는 해보고 싶었던 모든 새로운 스택에 도전해서, 스택에 대한 낯섬을 해소하고, 이해와 결과물을 얻었다는게 좋았습니다.

아쉬웠던 부분

  1. 처음 적용하는 스택의 설정과 낯설음 등에 의해서 초기에 시간과 에너지를 너무 소비하였고, 결국 과제를 미완성으로 끝마치게 된게 아쉬웠습니다.

  2. 미완성 뿐만 아니라 전체적으로 코드가 완성도가 많이 떨어진다고 느꼈습니다. 로직이 너무 분리되지 않고 구현을 위한 코드같은 느낌이였습니다.

    이런 부분은 시간적 여유가 생겨 리팩토링적인 여유가 있으면 물론 신경쓰겠지만, 그렇지 않더라도 코드를 작성할때 조금만 더 신경써서 코드의 로직을 분리하면 어땠을까 하는 생각을 했습니다.

  3. 구현적인 부분에서 로그인 / 로그아웃은 결함이 있는채로 작성된게 아쉬웠습니다. 기본적으로 json토큰의 유효기간을 처리를 하지 못해서 각 페이지에 대한 접근처리를 아예 빼버렸는데, 이부분도 middleware를 사용해서 처리하면 어땠을까 하는 아쉬움이 있었습니다.

  4. 클린코드적인 측면에서 Http를 비롯해서, 데이터를 관리하는것은 class로 해보고 싶었는데, 도중에 에러를 해결하지 못하여 다른 방법으로 우회해서 요구사항을 구현한게 아쉬웠습니다.

  5. 아직은 리액트 쿼리의 사용방법이 미숙하다고 느꼈습니다. useQueries를 사용할경우 여러 쿼리에 대한 비동기 방법을 아직은 잘 감이 안오는것 같습니다.
    미구현한 부분이 있기 때문에 구현을 못한것도 있지만, 하나의 객체에 캡슐화를 통해 CRUD를 완벽히 관리하였으면 좋았을것 같다고 생각하였습니다.

  6. TS에 대해서 아직 부족한점을 느꼈습니다. 일부데이터에서 끝내 해결하지 못해 any를 명시한게 있었습니다.

  7. 새로운 스택을 사용하는데 생각보다 너무 적응이 느리다고 느꼈습니다. 돌이켜보면 그저 하라는데로만 하면 되는건데, 너무 헤맸던것 같습니다.

배포 링크

(작업 기간 약 5.5일)
https://pre-onboarding-7th-3-2-9-three.vercel.app/
id : 1234@1234.com / pwd : 1234
json서버를 팀단위로 공용으로 사용하고있는데, 이 데이터 중 마지막 3페이지 정도 데이가 제가 작성한 코드의 타입과 맞지 않아 에러가 발생하는 이슈가 잇습니다.

Git Repo

https://github.com/jun-05/pre-onboarding-7th-3-2-9

BestPractice 선정 결과

완벽히 구현하신 팀원 분이 한분밖에 없으셔서, 그분이 선정되셨습니다.

진행 중 아쉬웠던 점

  • 팀원 중 두세분정도가 각자 사정이 생기셔서 원활하게 참여하지 못하셨던게 아쉬웠던것 같습니다.
  • 저도 비록 미완성으로 배포하긴 했지만, 다른분들도 미완성이더라도 시간내에 배포하는게 좋지 않았을까 하는 생각이 있었습니다.

진행 중 좋았던 점

  • 이번 프로젝트에서는 많은 분들이 Next와 React-query를 처음으로 사용하게 되었는데, 그러다보니 많은 부분에서 막히는 부분이 많았습니다. 하지만 막혔던 부분에 대한 해결방법 공유와 Repo공유 등, 기존 프로젝트중에서 가장 활발하게 정보를 공유했던것 같습니다.
  • 각자 프로젝트 진행상황 공유시간에 막히는 부분에 대해서 다 같이 의견을 내고 해결방안을 궁색하는 시간이 좋았던것 같습니다.

전체 프로젝트 마무리 소감

개인적인 팀 프로젝트 소감은 사실 한줄로도 충분할것 같습니다. 짧은 시간내에 몰입하여 개발하는 경험을 통해 많은것을 배우고, 공부 또는 개발하는 방식을 배울 수 있었던것 같습니다.

하지만 팀으로서의 프로젝트적인 면을 봤을때는 할말이 좀 많을것 같네요.
한달이 좀 안되는 시간내에 짧고 굵은 경험을 한것 같습니다.
지금까지는 대부분 혼자서 공부하거나, 클론 코딩을 하거나, 대부분 혼자하는 경우가 많았는데 생면부지의 팀원 8명이 랜덤으로 배치되 제로부터 시작해 6개의 프로젝트를 제출해가는동안 규칙을 만들고, 틀을 점차 형성해가면서 팀으로서의 성장을 느낄 수 있었던것 같습니다.

처음에는 BestPractice를 어떻게 뽑아야 하는지부터 몇시간을 헤매고, 토의한것에 대해선 전혀 정리하지도 못하였고, 코드의 형식 또한 제각각이였지만

팀으로서 진행해가며 토의에는 꼭 필요한 안건만 말하게 되면서 시간이 짧아지게 되었고,

토의한것은 정리해서 올려 언제든 볼수있게 하여, 토의에 참여 못 하였거나, 토의한 내용이 기억이 안나면 나중에라도 볼 수 있게 하였습니다.

만나는 시간은 처음에는 제각각이였지만, 점차 만나는 시간은 고정되어 안정되감을 느꼈고

readME 디자인이 부족한거 같아 다른팀의 readME에서 좋은 점을 가져와 readME 템플릿을 만들기도 하였습니다.(지나고 보니 약간 촌스럽긴 한거 같습니다.)

초기에는 팀원들간의 소통이 잘 안되어 답답하고, 스트레스를 받았던것도 사실이지만, 진행해가며 조금씩 팀으로서의 정체성 같은것을 느낄 수 있었던것 같습니다.

하지만 아쉬운 점도 있습니다.
저는 팀장을 맡아 진행하였는데, 사실 경험자체가 거의 없는 편이였기때문에 초기엔 토의가 불필요하게 길어졌던것 같습니다.

시간 분배도 초기엔 너무 생각없이 잡았던것도 있습니다. 오전 10시에 시작한 강의가 2시에 끝났는데 10분후에 보자고 한게 기억에 남습니다.

그리고 초기에 무리한 안건을 낸것 등도 기억에 남는 아쉬운점인것 같습니다. BestPractice의 코드를 다 같이 공부하자느니, 각자 팀원의 코드를 보 Discussion에 BestPractice라 생각하는 코드 부분을 적어 올리자느니 하는 부분이 있었습니다.

전체적으로 팀장으로서의 부담감 때문인지, 초기에는 더욱 오버페이스로 팀을 이끌어나가려고 했었던것 같습니다.

여러가지 시행착오가 있을뻔했지만, 그래도 그때마다 팀이라는 가치를 깨달을 수 있었습니다.

제가 이상할 수 있는 안건을 내더라도, 팀원분들이 그 안건에 대한 수정안을 말해주셨고, 그 의견들을 수렴해가며 하나의 규칙, 틀을 만들 수 있었던것 같습니다.

돌이켜 보면 잘한 부분, 못한 부분 모두 다 성장의 일부였다는 생각이 드네요.

0개의 댓글