프로그래머스 데브코스 - 12월 회고

지인혁·2024년 1월 21일
3
post-thumbnail

💨 들어가며

데브코스 1차 프로젝트가 12월 22일 ~ 1월 17일에 마무리가 되었다. 본 회고는 이 프로젝트에 대해 전체적인 내용으로 작성할 예정이다.

내가 생각했던 것 보다 새로운 기술이나, 협업에서의 중요성, 프로젝트의 완성도를 높이는 방법 등 많은 생각을 느낀 프로젝트였다고 생각하기 때문이다.


🚀 5명의 프론트엔드 협업

프로젝트 베포 URL

이전에 경험한 프로젝트와는 달리 5명의 팀원과 협업하는 프로젝트는 처음이였다. 그래서 협업의 방식이 매우 중요했다.

협업의 툴과 방식 컨벤션 등 이러한 팀원들과의 초기 설정이 매우 중요했고 실제로도 잘 적용하여 활용되었다고 생각되었다.

그리고 초기에 팀원들과 재사용 컴포넌트, 커스텀 훅 등 공용으로 사용 될 부분을 회의를 통해 지속적으로 추상화하여 처음에는 재사용 부분을 같이 작업을 하기 때문에 비소 느리게 보였지만 공용 로직 작업이 끝난시점에는 편하게 재사용하면서 개발 속도가 매우 빨라졌다.

나만 사용하는 코드가 아닌 팀원들이 다 같이 사용하는 코드이다 보니 어떻게 하면 팀원들이 더 가독성 좋게 코드를 읽을 수 있을까, 어떻게 하면 팀원들이 재사용에 있어 더 편리하게 코드를 작성할 수 있을까 정말 끊임없이 고민했던 것 같다. 사실 상 이렇게까지 생각하면서 만들어보긴 처음이지 않을까 싶다.

interface Props extends InputHTMLAttributes<HTMLInputElement> {
  label?: string;
  block?: boolean;
  errorMessage?: string;
  wrapperProps?: HTMLAttributes<HTMLDivElement>;
}

const Input = forwardRef(
  (
    { label, block = false, wrapperProps, errorMessage, ...props }: Props,
    ref: ForwardedRef<HTMLInputElement>,
  ) => {
    return (
      <StyledWrapper
        $block={block}
        {...wrapperProps}
      >
        <StyledContainer>
          {label && <StyledLabel>{label}</StyledLabel>}
          {errorMessage && (
            <StyledErrorMessage>{errorMessage}</StyledErrorMessage>
          )}
        </StyledContainer>
        <StyledInput
          $invalid={!!errorMessage}
          ref={ref}
          {...props}
        />
      </StyledWrapper>
    );
  },
);

Input.displayName = 'Input';

export default Input;

만들어 본 Input 컴포넌트이다. 처음에 Input에 올만한 속성들을 props로 다 받았으나 InputHTMLAttributes의 상속을 통해 기존 Input에 관련된 속성들은 상속으로 다 처리해서 중복을 방지했다.

하지만 몇 가지 문제점이 보인다. Input 공용 컴포넌트를 사용할 때 label과 errorMessage의 위치가 고정점이라는 점과 하나의 컴포넌트에서 label, errorMessage, input 3개의 책임을 가지기에 어떻게 보면 단일 책임 원칙에 맞지 않다.

프로젝트에서는 적용하지 아직 적용하지 않았지만 고민끝에 컴포넌트 컴포지션, 즉 컴포넌트 합성이라는 개념을 알게되었다. 컴포지션을 통해 책임을 분리할 수 있고 각 책임에는 관심있는 데이터만 가질 수 있다.

리팩토링이나 추후 같은 문제를 마주하면 컴포지션 방법을 적극적으로 활용해 더 나은 컴포넌트 설계를 작업할 예정이다.


✨ React Query

이번 프로젝트에서 React Query를 사용해보자고 추천했었다. 팀원들은 처음 사용해봐서 꺼리낌이 있었지만 사용법도 간단하고 서버 데이터를 관리하는 기능을 정말 추천해드리고 싶었다.

하지만 프로젝트에서 처음 사용할 때 당황했엇다. React Query의 이름과 사용법이 달라진 점이다. 그래서 나도 처음에 애를 좀 먹었다. 더군다나 v5 이상 버전의 정보도 많이 있지 않았다.

그래도 공식 문서를 보면서 차근차근 새로운 문법을 익혀나가며 팀원들과 공유를 통해 프로젝트에 잘 적용해나갈 수 있었다.

// useMutation
const { mutate, status } = useMutation({
    mutationFn: (loginFormData: PostLoginRequestType) => login(loginFormData),
    onSuccess: (response) =>
      response ? onSuccessCallback(response) : onErrorCallback(),
});
  
// useQuery
const { data: searchUserList, isLoading: searchIsLoading } = useQuery({
    queryKey: [QUERY_KEYS.SEARCH_USER_LIST, values.userName],
    queryFn: () => searchUsers(values.userName),
    enabled: isSubmit && !errors.userName,
});

const { data: onlineUserList, isLoading: allUserIsLoading } = useQuery({
    queryKey: [QUERY_KEYS.ONLINE_USER_LIST],
    queryFn: getOnlineUsers,
    refetchInterval: refetchTime,
});

🧠 Axios interceptors

JWT를 통해 로그인 인증을 구현해야했다. header에 토큰 값을 넣어줘야했는데 이전에는 수 작업으로 header 객체에 접근하여 추가하고 제거했다.

근데 팀원 중 한 분이 API 함수 구현 담당을 맡으셨는데 이때 interceptors를 사용해서 jwt 헤더를 관리하셨다. 신기해서 좀 더 개인적으로 찾아보고 팀원에게 질문도 했었는데 정말 좋은 axios의 속성인 것 같았다.

interceptors를 사용하면서 매 요청을 하기 전 interceptors가 요청을 가로채어 특별한 로직을 수행할 수 있었는데 jwt를 구현하기에 정말 유용했다.

멘토님께서도 interceptors를 사용하시길 권장하셨다. 나에게는 새로운 기술이였고 정말 본 프로젝트를 통해 많은 것을 배우고 느끼게 되는 것 같았다.

const setAuthorization = (config: InternalAxiosRequestConfig) => {
  const accessToken = JSON.parse(sessionStorage.getItem(AUTH_TOKEN_KEY) || '');

  if (accessToken) {
    config.headers.Authorization = `Bearer ${accessToken}`;
  }

  return config;
};  
  
axiosAuthInstance.interceptors.request.use(setAuthorization, (error) => {
  console.error(error);
  return Promise.reject(error);
});

👀 UX 및 성능 최적화를 통해 완성도 높은 프로젝트

우리 팀원들은 기본적인 기능을 다 완성하고도 항상 사용자의 UX면에서나 성능의 최적화 부분에서도 어떻게 하면 더 효율적으로 개선할 수 있을까 같이 고민했다.

뭐 버튼 클릭 시 UI의 변경이나 Alert 창의 유무, 에러 처리, 디바운싱, 디자인 개선 등 기능이 완성되어도 완성도 높은 프로젝트를 위해 꾸준히 개선해나가며 이전보다 퀄리티 좋은 프로젝트를 만들기 위해 엄청 노력했다.

이 경험으로 나도 어떻게하면 사용자 입장에서 더 편하게, 더 성능 좋게, 더 효율적이게 서비스가 동작할 방법이 무었이 있을까? 깊게 생각해 본 프로젝트였다.


🧷 Infinity Query

React Query에서 제공하는 옵션 중 하나다.

구현할 기능 중에 무한 스크롤 페이지 네이션 기능이 있었다. 처음에 offset 값을 상태로 관리하며 offset 값을 무한 스크롤을 통해 값을 조절했다.

하지만 동작은 잘 되었는데 뭔가 매끄럽지 않았고 코드도 매우 깔끔하지 못해서 만족하지 못했다. 그리고 알 수 없는 키 중복 오류까지 더해져 성능까지 비효율적으로 수정하게 되었다.

일단 기능이 동작하게 구현했지만 어떻게하면 가독성과 성능을 개선할 수 있을지 고민하다가 Infinity Query를 알게되었다.

더불어 팀원 중 한 분도 무한 스크롤을 구현하고 있었고, 같이 지식이나 정보를 공유하여 Infinity Query를 통해 정말 깔끔하게 페이지 네이션을 구현할 수 있었다.

const {
    data: userList,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage,
  } = useInfiniteQuery({
    queryKey: [QUERY_KEYS.USER_LIST],
    queryFn: ({ pageParam }) => getUsers({ offset: pageParam, limit }),
    initialPageParam: 0,
    getNextPageParam: (lastPage, allPages) =>
      lastPage?.length ? allPages.length * limit : null,
    select: ({ pages }) =>
      pages.flatMap(
        (page) => page?.filter((user) => user?.role !== 'SuperAdmin') ?? [],
      ),
  });  

🙌 마치며

1주의 기획 및 초기 설정 디자인 등, 2주 간의 개발, 총 3주의 짧은 프로젝트 기간이였다. 팀원들 모두 열심히 해주셔서 무사히 마무리 할 수 있는 프로젝트였고 또한 팀원들에게 많이 배우고 가는 프로젝트였다.

완벽하게는 당연히 아니지만 확실히 데브코스 이전과 비교했을 때 얻게 된 지식이 매우 많다. 특히 강의에서 배운 내용들을 실제 프로젝트에서 유용하게 사용했고 이전의 나의 나쁜 코드 습관과 생각없이 작성한 코드를 생각하면 많이 성장했다고 느낀다.

이제 곧 마지막 2차 프로젝트를 바라보고 있지만 아직 배우고 싶은 것, 궁금한 게 너무 많다. 특히 Next.js가 13v 이상 버전부터 많이 달라졌다고 하는데 궁금하다. 그래서 관련된 강의나 프로젝트를 해볼예정이다.

또한 협업을 통해 어떻게 하면 더 읽기 좋고 사용하기 좋은 코드를 작성하는지도 욕심이 더 생겼다. 이 부분도 더 공부를 해볼 예정이다.


profile
대구 사나이

2개의 댓글

comment-user-thumbnail
2024년 1월 24일

인혁님 고생많으셨어요!! 세희팀 CTO...

답글 달기
comment-user-thumbnail
2024년 1월 24일

👍

답글 달기