과제 체크포인트
https://nemobim.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가 정상적으로 작동하는가?
최종과제
- 폴더구조와 나의 멘탈모데일이 일치하나요?
- 다른 사람이 봐도 이해하기 쉬운 구조인가요?
과제 셀프회고
PR 타이틀 옆에 양말이 보이시나요 🧦
이번 과제를 진행하면서 팀원들과도 자주 토론했지만 다른 팀의 팀원들과도 소통해보고 싶어서 정석님이 만드신 구린내 클럽에 가입했습니다. 구린 코드를 치게 되더라도 그게 초보라는 의미가 아니다! 그냥 주머니에 냄새나는 양말 하나 쑤셔넣은거니 낙심말자 라는 취지였습니다.
<구린내 원칙>
- zustand 쓰기(반조타이파) //였으니 시간 부족으로 조타이를 써버렸습니다 클럽원들에게 심심한 사과의 말을 전하며...
- 코드리뷰 해주기
- 구린내 유지하기 (대신 양말은 주머니에 넣기, 티내기 금지)
- 설명은 민준이한테 하듯이..민준이 다수 보유 클럽(민준이란 개발 도메인 지식이 전혀없는 초등학생롤이다)
FSD 관련해서 재밌는 얘기 많이 주고 받았습니다.....구클 짱
이번 과제를 통해 이전에 비해 새롭게 알게 된 점이 있다면 적어주세요.
FSD(Feature-Sliced Design) 가 단순히 폴더구조 나누기가 아니다! 그냥 구조만 바꾸는 줄 알았으나 "계층별 책임을 기준으로 폴더를 정리" 하는 심오한 작업이었습니다. 서로의 역할이 섞이지 않도록 코드를 구조화할 수 있어 마치 울타리가 생긴 느낌입니다. 덕분에 '왜 이렇게 나누는지'에 대해 생각해볼 수 있었습니다. 구조를 먼저 고민하는 게 얼마나 중요한지도 조금은 감이 잡혔습니다.
본인이 과제를 하면서 가장 애쓰려고 노력했던 부분은 무엇인가요?
FSD를 도입하기 위해 관심사가 적절히 분리 됐는가? 의존적이지 않고 독립적인가? 를 중점으로 고려했습니다.
- 관심사 분리: 코드가 주제별로 섞이지 않기! 그래야 계층과 위치를 신경 써서 유지할 수 있음.
- props drilling 최소화: 서로의 의존도를 낮추고 깊은 전달을 줄이기 위해 전역 상태를 활용해보기
- 복잡한 상태 구조 개선: 데이터 흐름을 단순화하고 예측 가능하게 만들기 위해 상태 구조를 계속 다듬기
그럼에도 완전히 독립적인 컴포넌트 만들기는 여전히 어렵습니다... 검색어나 필터처럼 여러 컴포넌트가 함께 사용하는 값은 전역으로 관리할 수밖에 없었고 더미 API 환경에서 post와 user 데이터를 결합하다 보니 원래라면 분리할 수 있는 로직이 한곳에 섞이기도 했습니다.
무엇보다 가장 어려웠던 건 "어떤 코드를 어떤 레이어에 둘 것인가" 에 대한 판단인거 같아요. 비즈니스 로직이 섞인 UI 컴포넌트를 어떻게 분리할지가 FSD 하는 내내 고민이됐습니다.
ex) 여러 feature에서 공통으로 사용하는 로직을 shared에 둬야하는걸까? 아니면 여기 페이지 한정으로 보고 entities에 둘까? FSD의 목표가 ‘나만 이해하는 구조’가 아니라 다른 사람이 봤을 때도 납득 가능한 흐름을 만드는 것이라고 생각해서 누가 봐도 일관성 있게 느껴질 수 있도록 코드를 짜려고 고민하다 이거 저거 해보면서 시행착오가 많았습니다.
아직은 막연하다거나 더 고민이 필요한 부분을 적어주세요.
의존성 방향과 레이어 간의 관계 설정
FSD에서는 상위 레이어가 하위 레이어를 참조하는 건 괜찮지만 그 반대는 지양해야 한다는 원칙이 있는 걸로 알고있습니다. 개념적으로는 납득이 되지만 실제로 개발하면서 이 경계를 항상 지키는게 쉽지 않더라구요. 기능이 늘어나고 폴더 구조가 깊어질수록 어떤 레이어가 어떤 레이어에 의존해도 되는지 매번 헷갈리고 무심코 잘못된 방향으로 참조를 한 경우도 있었습니다. 특히 공통 유틸이나 로직이 여러 레이어에 걸쳐 쓰이는 상황에서는 아직도 판단이 어려워요..
컴포넌트 재사용 범위의 적정선 찾기
컴포넌트를 얼마나 범용적으로 만들지 혹은 특정 상황에만 맞게 만들지의 경계가 고민이 됩니다.
- 너무 범용적으로 만들면 컴포넌트 자체가 복잡해져 오히려 사용하기 어려움
- 반대로 너무 특정 화면에 맞춰 구성하면 다른 곳에서는 활용하기 힘듦 이걸 shared에 넣어야 돼,,말아야 돼 하는 고민,, 이번 과제는 단일 페이지였지만, 확장성을 염두에 두고 설계하다 보니 이 부분에서 특히 많은 고민한거 같아요.
TanStack Query의 쿼리 키 관리 전략
데이터가 단순할 때는 큰 문제가 없었지만 searchParams처럼 여러 상태와 연관된 데이터를 다루기 시작하면서 쿼리 키 관리가 복잡해졌습니다. 처음엔 listParams와 list를 각각 분리된 키로 관리했는데, 더미 API의 구조상 params가 완전히 일치하지 않으면 기존 캐시를 재사용하지 못하는 문제가 있었습니다. 결국 CRUD 처리 과정에서 검색 데이터를 인자로 받아 쿼리 키를 가져와서 캐시 데이터를 컨트롤 했는데 이 방식이 올바른지 확신이 들지 않았습니다....
이번에 배운 내용 중을 통해 앞으로 개발에 어떻게 적용해보고 싶은지 적어주세요.
-
코드는 일관성 있게 작성하자 누구든지 코드를 봤을 때 흐름이 일정하게 느껴질 수 있도록, 파일 구조, 명명 방식, 레이어 구분 등에 일관성을 유지하는 데 더 신경 쓰려고 합니다. 이번 과제를 하면서 낯선 구조보다는 익숙하고 일관된 패턴이 유지보수에 훨씬 유리하다는 걸 직접 느낄 수 있었습니다.
-
관심사는 분리하자 내부 로직을 굳이 열어보지 않아도, 파일 이름이나 폴더 위치만으로 기능이 유추되도록 설계하는 것을 목표로 하고 있습니다. 기능별로 책임이 명확히 나뉘어 있어야 디버깅이나 기능 추가도 수월하고 협업할 때도 서로의 코드를 이해하기 훨씬 쉬워진다고 느꼈습니다.
-
컴포넌트는 서로 의존하지 않도록 만들자 하나의 컴포넌트가 다른 컴포넌트의 내부 상태나 구현 방식에 의존하지 않도록 최대한 독립적으로 구성하고자 합니다. 이렇게 해야 테스트나 리팩터링 부담이 줄고 다른 화면이나 프로젝트에서도 쉽게 재사용할 수 있다는 점에서 실용적이라고 생각합니다.
-
컴포넌트를 만들 때는, 재사용보다 ‘역할’을 먼저 정의하자 그동안은 재사용 가능한 컴포넌트를 만들겠다는 생각에 너무 집중하다 보니 오히려 컴포넌트가 과하게 일반화되고 복잡해지는 경우가 많았습니다. 실제로는 한두 군데에서만 쓰이는데, 과도하게 추상화한 적도 있었고요. 앞으로는 먼저 **"이 컴포넌트가 어떤 데이터를 받고, 어떤 UI만 책임지는가?"**를 기준으로 역할을 명확히 정의하고, 그 역할에 집중하는 방향으로 설계하려 합니다. UI는 UI대로, 로직은 훅이나 유틸 함수로 분리하는 구조를 꾸준히 반복하다 보면 테스트나 유지보수에서도 훨씬 효율적인 코드를 만들 수 있을 거라 생각합니다.
챕터 셀프회고
클린코드와 아키테쳑 챕터 함께 하느라 고생 많으셨습니다! 지난 3주간의 여정을 돌이켜 볼 수 있도록 준비해보았습니다. 아래에 적힌 질문들은 추억(?)을 회상할 수 있도록 도와주려고 만든 질문이며, 꼭 질문에 대한 대답이 아니어도 좋으니 내가 느꼈던 인사이트들을 자유롭게 적어주세요.
클린코드: 읽기 좋고 유지보수하기 좋은 코드 만들기
- 더티코드를 접했을 때 어떤 기분이었나요? ^^; 클린코드의 중요성, 읽기 좋은 코드란 무엇인지, 유지보수하기 쉬운 코드란 무엇인지에 대한 생각을 공유해주세요
더티 코드를 마주했을 때 가장 먼저 드는 생각은 “어디서부터 봐야 하지...” 하는 막막함이었습니다. 구조가 한눈에 들어오지 않거나 변수와 함수명이 추상적이라 역할이 잘 드러나지 않는 코드를 보면 금방 피로해지고, 특히 하나의 파일에 수백 줄이 넘는 코드가 기능별로 나뉘지 않고 길게 이어져 있을 경우 이리저리 흐름을 따라가며 전체 기능을 파악하는 게 복잡했던거 같습니다.
클린코드란 결국 "이해하기 쉬운 코드"라 생각됩니다. 변수명과 함수명만 봐도 역할과 의도가 명확하게 드러나고 읽는 사람이 별다른 설명 없이도 자연스럽게 코드의 흐름을 따라갈 수 있어야 진짜 클린코드라고 생각됩니다.
이번 과제를 하면서 구조와 네이밍을 반복해서 다듬다 보니, 읽기 좋은 코드란 단순히 '무엇을 한다'가 아니라 '왜 이렇게 했는지'까지 자연스럽게 드러나는 코드라는 걸 체감했습니다. 이름을 잘 짓는 것도 물론 중요하지만 함수의 위치나 분리 기준, 폴더 구조 같은 부분에서도 개발자의 의도와 판단 기준이 읽히는 구조를 만드는 것이 정말 중요하다고 느꼈습니다.
주석 없이도 맥락이 보이는 코드를 쓰기 위해서는 네이밍뿐 아니라 구조 설계에도 충분한 고민이 필요하다. 코드를 짤 때 **‘이 코드를 읽는 사람이 내가 왜 이렇게 구성했는지까지 자연스럽게 이해할 수 있을까?’**를 늘 염두에 두자.
결합도 낮추기: 디자인 패턴, 순수함수, 컴포넌트 분리, 전역상태 관리
- 거대한 단일 컴포넌트를 봤을때의 느낌! 처음엔 막막했던 상태관리, 디자인 패턴이라는 말이 어렵게만 느껴졌던 시절, 순수함수로 분리하면서 "아하!"했던 순간, 컴포넌트가 독립적이 되어가는 과정에서의 깨달음을 들려주세요
솔직히 이 부분은 매번 어려워요.. 거대한 단일 컴포넌트를 마주하면 어디서부터 손대야 할지 막막하고 흐름을 따라가며 분리하는 데에도 시간이 꽤 많이 듭니다. 처음에는 상태 관리나 디자인 패턴이라는 개념도 어렵게 느껴졌고 그냥 동작하게 만드는 것에만 집중했던 것 같습니다.
이번 과제에서도 우선은 의존성이 가장 적은 UI나 state부터 분리하는 방식으로 접근했습니다. 가장 먼저 나눈 건 기능 없이 시각적인 역할만 하는 UI 조각들이었고, 그런 컴포넌트부터 분리해내고 나니 전체 구조가 조금씩 눈에 들어오기 시작했습니다. 이후에는 props를 넘기더라도 일단 관심사를 분리해내는 데 초점을 맞췄습니다.
그리고 어느 정도 UI를 걷어내고 나면 결국 남는 건 상태와 로직입니다. 이때부터는 “이 state가 이 컴포넌트에서만 필요한가?”, “다른 컴포넌트에서도 쓰이나?”를 기준으로 판단하면서, 전역 상태로 올릴지, 지역 상태로 남길지를 결정했습니다. 이 과정에서는 상태 관련 로직을 가능한 순수 함수로 분리해, UI와 로직이 섞이지 않도록 의식적으로 나누려 했습니다.
이런 구조 변경 과정에서 가장 중요하게 생각한 건 기존 기능이 무너지지 않도록 하는 것이었습니다. 평소라면 테스트 코드를 돌려가며 확인하면서 분리 작업을 진행하는데 이번 과제는 테스트 없이 시작해서 잘못된 경우 되돌리는 데 시간이 꽤 걸리기도 했습니다... 역시 구조를 바꿀 때는 작은 단위로 나누고, 그때마다 기능을 확인하며 진행하는 방식이 가장 안전하다는 걸 다시 느꼈습니다...
3주간 리팩토링을 반복하면서 느낀 건, 완벽한 구조를 한 번에 만들려 하기보다는, 작은 개선을 꾸준히 쌓아가는 게 훨씬 현실적인 방법이라는 점이었습니다. 처음엔 복잡하게 보이던 컴포넌트도, 이전보다 나아졌다면 그 자체로 충분히 의미 전진이라 생각합니다. 그런 과정을 계속 겪다 보니, 어느 순간 “아, 이제 좀 보이네” 싶은 느낌이 들었습니다. 각 컴포넌트가 자신의 역할만 명확히 책임지게 만들때, 사람들이 왜 ‘좋은 구조’에 대해 반복해서 이야기하는지 조금은 실감할 수 있었던 주차였습니다.
응집도 높이기: 서버상태관리, 폴더 구조
- "이 코드는 대체 어디에 둬야 하지?"라고 고민했던 시간, FSD를 적용해보면서의 느낌, 나만의 구조를 만들어가는 과정, TanStack Query로 서버 상태를 분리하면서 느낀 해방감(?)등을 공유해주세요
FSD를 이번에 처음 접하고 적용해봤는데 사실 아직도 이 방식이 정말 더 좋은 건지는 잘 모르겠습니다. 저는 평소에 폴더 뎁스를 깊게 타는 걸 좋아하지 않고 기존에는 components나 pages 폴더 아래에 용도별로만 파일을 나눠 사용하는 식으로 정리해왔습니다. 그래서인지 FSD는 오히려 폴더 구조를 복잡하게 만든다는 인상을 받았고, 실제로 코드를 작성하는 시간보다 "이 파일을 어디에 넣어야 하지?" 고민하는 데 더 많은 시간을 쓰는거 같아.. 주객전도 된 느낌이 들었습니다.
그래도 작업을 하면서 이 구조가 말하려는 의도는 점점 체감하게 됐습니다. 단순히 컴포넌트를 만드는 게 아니라 앞으로 이 컴포넌트가 어떤 역할을 맡게 될지, 어디에서 재사용될 수 있을지 등을 더 고민하게 되는게 장점같습니다. 특히 규모가 커질수록 코드의 위치와 역할을 명확히 나눌 수 있다는 점이 대규모 프로젝트에선 좋은 기준점이 될 수 있을 거라는 생각도 들었습니다.
실제로 작업할 땐 저만의 기준을 잡아가며 분리하려고 했습니다:
- features: 상태 변경이나 사용자와의 상호작용이 있는 컴포넌트
- entities: 순수하게 데이터를 받아 렌더링만 하는 컴포넌트, features에서 사용하는 로직이나 타입도 포함
- shared: 전역에서 재사용 가능한 상수, 스타일, 단순 UI 조각
- widgets: 여러 features를 조합한 UI 단위
하지만 막상 적용하다 보면 여전히 헷갈리는 지점이 많았습니다.
예를 들어 버튼 컴포넌트를 entities에 넣었더니 모든 동작을 props로 넘겨야 해서 "이게 맞나?" 하는 의문이 생기기도 했고, 공통으로 쓰이는 타입이나 훅의 위치도 features와 entities 중 어디에 둬야 하나 고민되는 경우가 많았습니다.
결국 제 기준은 가능한 로직은 entities에 두고, features에서는 그걸 조합해 사용하는 방식으로 잡게 됐습니다. 또 features 간에 조합이 필요한 경우는 widgets로 올리는 식으로 정리했는데 예컨대 게시판 상세와 댓글 정보를 하나의 다이얼로그로 묶어야 할 때 그게 화면 단위 기준으로 위젯에 넣는 게 맞는지 아니면 기능 기준으로 각각 분리하는 게 맞는지가 가장 헷갈리는 지점이었습니다. 결국 위젯으로 올리긴 했지만 예전 방식대로라면 그냥 하나의 feature 안에 두었을 상황이라 FSD 기준을 내가 맞게 하는건가 하는 의문이 많이 들었습니다..
그래도 이 과정을 거치며 느낀 건 폴더 구조 자체보다 더 중요한 건 코드의 책임을 어떻게 나눌 것인가에 대한 기준인거 같습니다. FSD는 정답이 있다기보다는 개발자가 계속해서 역할과 위치를 질문하게 만드는 구조라는 점에서 의미가 있었고 단방향 의존성이나 관심사 분리처럼 챙겨갈 만한 원칙도 분명히 있었다고 생각합니다.
TanStack Query은 도입했을 때는 확실히 서버 데이터 상태 관리가 가벼워집니다.기존에는 서버 데이터를 전부 state로 관리하며, 조회·로딩·에러·리패치 같은 부가 상태까지 직접 처리해 UI 상태와 서버 상태가 뒤섞여 구조가 복잡해지고 컴포넌트가 무거워지거나 데이터 흐름이 꼬이는 경우도 많았습니다.
TanStack을 적용하면 훅으로 데이터를 추상화해 컴포넌트는 화면 렌더링에만 집중할 수 있었고 관심사도 뚜렷하게 분리할 수 있습니다.조회, 리패치, 무효화 같은 로직이 Query 단에서 자동 처리되면서 화면 코드는 훨씬 간결하고 읽기 쉬워집니다.
이번 과제는 더미 API 환경이라 모든 장점을 체감하긴 애매하지만, 예를 들어 게시글을 등록한 뒤 리스트를 다시 불러오는 상황에서 복잡한 상태 전파 없이 쿼리 무효화 한 줄로 리패칭을 처리할 수 있다는 점은 확실히 강점이라 생각됩니다. 서버 상태가 클라이언트 로직에 섞이지 않고 기능별로 깔끔하게 모듈화된 구조를 만들 수 있다는 점에서 FSD와도 궁합이 잘 맞는 도구라는 생각도 들었습니다.
리뷰 받고 싶은 내용이나 궁금한 것에 대한 질문
1. 여전히 FSD가 어려운거 같아요. 특히 단방향 의존성 부분이요... 의존성을 지키기 위해 상위에서 쓰는 로직들을 하위로 내리기도 했는데 이 방식이 맞을까 하는 의문이 있습니다. FSD가 정답은 없다고 하지만 어느정도 룰을 지키는게 중요하다고 보는데 이렇게 의존성을 지키기 위해 계층을 업다운 하는게 맞는지..궁금합니다.
2. 이번에 더미 API 때문에 검색 쿼리를 전역상태로 관리해야했나? 하는 고민이 듭니다. 원래는 지역이 맞다고 보는데 쿼리키에서 params를 넣어줘야 해당 캐시를 가져올 수 있어 prop로 전부 내려주는 작업을 했습니다. 그리고 제가 만든 검색쿼리 hook이 재사용을 고려하고 만든거라 리스트 검색하나만을 위해 또 동일한 기능의 훅을 만드는게 맞는건지도 좀 고민이 됐습니다.. 좋은 해결책이 있었을까요?
3. 컴포넌트를 쪼갤지 말지 판단하는 코치님만의 기준이 있으신가요?
과제 피드백
안녕하세요 도은님! 클린코드 마지막 과제 잘 진행해주셨네요 ㅎㅎ 고생하셨습니다!!
- 여전히 FSD가 어려운거 같아요. 특히 단방향 의존성 부분이요... 의존성을 지키기 위해 상위에서 쓰는 로직들을 하위로 내리기도 했는데 이 방식이 맞을까 하는 의문이 있습니다. FSD가 정답은 없다고 하지만 어느정도 룰을 지키는게 중요하다고 보는데 이렇게 의존성을 지키기 위해 계층을 업다운 하는게 맞는지..궁금합니다.
흠.. 말씀해주신 부분을 알려주시면 판단하기가 쉬울 것 같은데요.. "의존성을 지키기 위해 상위에서 쓰는 로직을 하위로 내리는 경우"를 현재 코드에서 찾기가 어렵네요 ㅎㅎ ㅠㅠ
상위 계층에서 정의된 "이름"을 그대로 하위 계층으로 내리기보단 인터페이스를 통해 소통하는 방법이 있답니다! 인터페이스를 통해 소통한다거나!?
- 이번에 더미 API 때문에 검색 쿼리를 전역상태로 관리해야했나? 하는 고민이 듭니다. 원래는 지역이 맞다고 보는데 쿼리키에서 params를 넣어줘야 해당 캐시를 가져올 수 있어 prop로 전부 내려주는 작업을 했습니다. 그리고 제가 만든 검색쿼리 hook이 재사용을 고려하고 만든거라 리스트 검색하나만을 위해 또 동일한 기능의 훅을 만드는게 맞는건지도 좀 고민이 됐습니다.. 좋은 해결책이 있었을까요?
검색쿼리가.. 주소의 쿼리를 말씀하시는게 맞겠죠!? 일단 사용자가 입력하는 값 자체는 로컬에서 관리하고, 입력이 완료되면 주소에 반영하는 한 다음에 주소의 값을 다른 곳에서도 가져다 사용하는 방식을 생각해볼 수 있을 것 같아요!
- 컴포넌트를 쪼갤지 말지 판단하는 코치님만의 기준이 있으신가요?
처음부터 쪼개는 편은 아닌 것 같아요 ㅎㅎ 부모에서 정의된 상태가 특정 영역에서만 쓰인다면, 해당 상태와 컴포넌트를 같이 분리한다거나!? (렌더링 관점) 혹은 재사용보단 목적에 따라 분리하는 편인 것 같습니다.
- 재활용할 수 있다면 분리하고
- 렌더링 여파에 따라 분리하고
- 목적과 역할에 따라 분리하고
보통 이렇게 세 가지로 구분되는 것 같습니다..!