jeongmingi123 님의 상세페이지[2팀 정민기] Chapter 2-3. 관심사 분리와 폴더구조

과제 체크포인트

배포 : https://jeongmingi123.github.io/front_6th_chapter2-3/

기본과제

목표 : 전역상태관리를 이용한 적절한 분리와 계층에 대한 이해를 통한 FSD 폴더 구조 적용하기

  • 전역상태관리를 사용해서 상태를 분리하고 관리하는 방법에 대한 이해
  • Context API, Jotai, Zustand 등 상태관리 라이브러리 사용하기
  • FSD(Feature-Sliced Design)에 대한 이해
  • FSD를 통한 관심사의 분리에 대한 이해
  • 단일책임과 역할이란 무엇인가?
  • 관심사를 하나만 가지고 있는가?
  • 어디에 무엇을 넣어야 하는가?

체크포인트

  • 전역상태관리를 사용해서 상태를 분리하고 관리했나요?
  • Props Drilling을 최소화했나요?
  • shared 공통 컴포넌트를 분리했나요?
  • shared 공통 로직을 분리했나요?
  • entities를 중심으로 type을 정의하고 model을 분리했나요?
  • entities를 중심으로 ui를 분리했나요?
  • entities를 중심으로 api를 분리했나요?
  • feature를 중심으로 사용자행동(이벤트 처리)를 분리했나요?
  • feature를 중심으로 ui를 분리했나요?
  • feature를 중심으로 api를 분리했나요?
  • widget을 중심으로 데이터를 재사용가능한 형태로 분리했나요?

심화과제

목표: 서버상태관리 도구인 TanstackQuery를 이용하여 비동기코드를 선언적인 함수형 프로그래밍으로 작성하기

  • TanstackQuery의 사용법에 대한 이해
  • TanstackQuery를 이용한 비동기 코드 작성에 대한 이해
  • 비동기 코드를 선언적인 함수형 프로그래밍으로 작성하는 방법에 대한 이해

체크포인트

  • 모든 API 호출이 TanStack Query의 useQuery와 useMutation으로 대체되었는가?
  • 쿼리 키가 적절히 설정되었는가?
  • fetch와 useState가 아닌 선언적인 함수형 프로그래밍이 적절히 적용되었는가?
  • 캐싱과 리프레시 전략이 올바르게 구현되었는가?
  • 낙관적인 업데이트가 적용되었는가?
  • 에러 핸들링이 적절히 구현되었는가?
  • 서버 상태와 클라이언트 상태가 명확히 분리되었는가?
  • 코드가 간결하고 유지보수가 용이한 구조로 작성되었는가?
  • TanStack Query의 Devtools가 정상적으로 작동하는가?

최종과제

  • 폴더구조와 나의 멘탈모데일이 일치하나요?
  • 다른 사람이 봐도 이해하기 쉬운 구조인가요?

과제 셀프회고

이번 과제를 통해 이전에 비해 새롭게 알게 된 점이 있다면 적어주세요.

본인이 과제를 하면서 가장 애쓰려고 노력했던 부분은 무엇인가요?

아직은 막연하다거나 더 고민이 필요한 부분을 적어주세요.

이번에 배운 내용 중을 통해 앞으로 개발에 어떻게 적용해보고 싶은지 적어주세요.

챕터 셀프회고

클린코드와 아키테쳑 챕터 함께 하느라 고생 많으셨습니다! 지난 3주간의 여정을 돌이켜 볼 수 있도록 준비해보았습니다. 아래에 적힌 질문들은 추억(?)을 회상할 수 있도록 도와주려고 만든 질문이며, 꼭 질문에 대한 대답이 아니어도 좋으니 내가 느꼈던 인사이트들을 자유롭게 적어주세요.

클린코드: 읽기 좋고 유지보수하기 좋은 코드 만들기

  • 더티코드를 접했을 때 어떤 기분이었나요? ^^; 클린코드의 중요성, 읽기 좋은 코드란 무엇인지, 유지보수하기 쉬운 코드란 무엇인지에 대한 생각을 공유해주세요

클린코드가 될려면 단일책임이 되어야 한다고 생각합니다. 또한 하나의 컴포넌트를 잘개 쪼개서 테스트 코드로 만들수있어야 한다고 생각합니다. feature 예제

interface OpenDetailPostProps {
  post: Post
}

export const OpenDetailPost: React.FC<OpenDetailPostProps> = ({ post }) => {
  const { openPostDetail } = useOpenDetailPost()

  return (
    <Button variant="ghost" size="sm" onClick={() => openPostDetail(post)}>
      <MessageSquare className="w-4 h-4" />
    </Button>
  )
}

jotai를 및 hooks를 사용하여 props를 줄였고 단일책임에 맞게 코드를 작성했다고 생각합니다. (맞는지는 모르겠음) 단일책임에 맞게 코드를 짜다보니, 좀 더 많이 생각하며 코드를 짜는거같아 이전에 비해 생각이 깊어진거같아 좋습니다.

결합도 낮추기: 디자인 패턴, 순수함수, 컴포넌트 분리, 전역상태 관리

  • 거대한 단일 컴포넌트를 봤을때의 느낌! 처음엔 막막했던 상태관리, 디자인 패턴이라는 말이 어렵게만 느껴졌던 시절, 순수함수로 분리하면서 "아하!"했던 순간, 컴포넌트가 독립적이 되어가는 과정에서의 깨달음을 들려주세요

피쳐와 엔티티, 위젯의 개념이 너무 헷갈렸으나, 다음과 같이 정의했습니다. 기능: 특정 목적을 위한 복합적인 사용자 행동 (검색, 필터링, 주문 등) -> 동사의 개념으로 이해 엔티티: 데이터 중심의 순수한 표현이므로 특별한 상호작용 로직이 없음 -> 따로 props에 handle함수가 순수 데이터만 있도록함. 위젯 : 화면 구획 내에서의 사용자 상호작용 (레이아웃 변경, 더보기 등) -> 전 피처와 엔티티를 합친 것을 위젯으로 이해함.

전반적으로 위의 개념처럼 이해를 했는데, 위젯부분이 맞는건지 모르겠어 조금 헷갈립니다. 다른분들의 코드를 많이 봐야겠고, 오픈소스로 올려진 외부 프로젝트를 많이 봐야겠습니다.

응집도 높이기: 서버상태관리, 폴더 구조

  • "이 코드는 대체 어디에 둬야 하지?"라고 고민했던 시간, FSD를 적용해보면서의 느낌, 나만의 구조를 만들어가는 과정, TanStack Query로 서버 상태를 분리하면서 느낀 해방감(?)등을 공유해주세요

tanstack/react-query를 사용하면서, 로직을 좀 더 간편화 할 수 있었고, 불편했던 로직을 매우 간소화 시킬 수 있어 좋았습니다.

export const useAddComment = (options?: Omit<UseMutationOptions<Comment, Error, NewComment>, "mutationFn">) => {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: commentApi.addComment,
    onSuccess: (data) => {
      // 해당 게시물의 댓글 쿼리를 무효화
      queryClient.invalidateQueries({ queryKey: ["comments", data.postId] })
    },
    ...options,
  })
}

// 댓글 수정
export const useUpdateComment = (
  options?: Omit<UseMutationOptions<Comment, Error, { id: number; body: string }>, "mutationFn">,
) => {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: ({ id, body }: { id: number; body: string }) => commentApi.updateComment(id, body),
    onSuccess: (data) => {
      // 해당 게시물의 댓글 쿼리를 무효화
      queryClient.invalidateQueries({ queryKey: ["comments", data.postId] })
    },
    ...options,
  })
}

// 댓글 삭제
export const useDeleteComment = (
  options?: Omit<UseMutationOptions<void, Error, { id: number; postId: number }>, "mutationFn">,
) => {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: ({ id }: { id: number; postId: number }) => commentApi.deleteComment(id),
    onSuccess: (_, variables) => {
      // 해당 게시물의 댓글 쿼리를 무효화
      queryClient.invalidateQueries({ queryKey: ["comments", variables.postId] })
    },
    ...options,
  })
}

// 댓글 좋아요
export const useLikeComment = (
  options?: Omit<UseMutationOptions<Comment, Error, { id: number; likes: number }>, "mutationFn">,
) => {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: ({ id, likes }: { id: number; likes: number }) => commentApi.likeComment(id, likes),
    onSuccess: (data) => {
      // 해당 게시물의 댓글 쿼리를 무효화
      queryClient.invalidateQueries({ queryKey: ["comments", data.postId] })
    },
    ...options,
  })
}

리뷰 받고 싶은 내용이나 궁금한 것에 대한 질문

image

안녕하세요. 다음과 같이 FSD 구조를 지었는데 맞는지 모르겠습니다. 이렇게하다보니 너무 잘개 쪼개져서, 컴포넌트가 많아진거같지만, 단일책임과 테스트는 좀 더 확고해졌다고 생각은하는데요. 하지만 너무 잘개 쪼갠느낌이라 맞는지 모르겠습니다.. ㅜㅜ

  1. 빨간색은 피쳐
  2. 파란색은 엔티티
  3. 초록색은 위젯

그리고 PostItem을 위젯으로 해야할지 엔티티를 해야할지 고민이 많았습니다. image EditIcon, TrashIcon, DetailIcon을 사용자의 상호작용이 있다 생각하여 저는 피쳐로 했고, PostItem 안의 제목, like, 작성자등은 엔티티로 따로 쪼갰습니다.

제가 궁금했던 것은 PostItem을 엔티티로 하게된다면, 단방향 흐름에 따라 엔티티안에 피쳐가 못온다는 부분을 알고있어서 PostItem을 위젯으로 했는데요. 제가 생각한 부분이 틀릴까요?

아니면 PostItem 전체를 엔티티로 두고 EditIcon, TrashIcon, DetailIcon도 그냥 엔티티로 두어야했을까요?

과제 피드백

안녕하세요 민기님! 클린코드 마지막 챕터 잘 진행해주셨네요 ㅎㅎ 고생하셨습니다!

제가 궁금했던 것은 PostItem을 엔티티로 하게된다면, 단방향 흐름에 따라 엔티티안에 피쳐가 못온다는 부분을 알고있어서 PostItem을 위젯으로 했는데요. 제가 생각한 부분이 틀릴까요? 아니면 PostItem 전체를 엔티티로 두고 EditIcon, TrashIcon, DetailIcon도 그냥 엔티티로 두어야했을까요?

widget으로 두거나 혹은 feature로 둬도 무방하다고 생각해요 ㅎㅎ 또 다른 방법은, PostItem이 아니라 TableItem 이라는 UI로 하나 추상화 한 다음에 여기에 entities나 features로 만들어진 데이터를 매핑하는거죠.

Icon은 widget과 동일한 레이어에 위치시키거나 혹은 shared/icon 같은 폴더를 만들어서 유지시켜도 좋았을 것 같네요!

여튼 정리하자면, 디자인시스템을 생각해보시면 좋은데요, 디자인 시스템은 다양한 UI의 단위를 만들어서 제공하고 있어요. 현재 과제에서도 Table과 TableItem 혹은 Tr 이라는 UI 요소를 추출할 수 있지 않을까!? 라는 생각이 드네요!

물론 이를 랩핑해서 사용하는 영역은 features 혹은 widgets가 맞다고 적절하다고 생각합니다 ㅎㅎ


위에 남긴건 저의 생각일뿐이고, 결국 제일 중요한건 민기님의 생각이라고 생각해요. 누군가가 "이렇게 코드를 작성한 이유가 있나요?" 라고 물어봤을 때 다른 사람의 생각을 기반으로 민기님의 생각을 전개할라고 하면 결국 말문이 막히게 된답니다.

민기님이 왜 이렇게 생각하게 되었는지에 대해 촘촘하게 고민해보시면 좋겠어요!