일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 타로엠비티아이
- tailwindcss
- 노마드코더
- 개발자북클럽
- 개발회고
- 사이드프로젝트
- select플러그인
- 웹개발
- 타로프로젝트
- 항해99
- book_club
- jqueryplugin
- 항해 플러스 프론트앤드
- 타로웹서비스
- 프론트엔드
- 항해플러스
- plugin
- 항해플러스프론트앤드
- 항해플러스 프론트앤드
- NextJs
- 프론트앤드
- niceselect사용법
- corser챌린지
- Supabase
- GeminiAPI 405
- 노개북
- 항해플러스 프론트엔드
- 프론트앤드개발
- whispersofthestars
- 프론트엔드사이드프로젝트
- Today
- Total
ㅇㅅㅇ
항해플러스 프론트엔드 6기 6주차 회고 본문
여섯 번째 도전기 - FSD와 TanStack Query로 코드 구조 개선하기
Chapter 2-3을 마무리하며 돌아보니, 이번 주차는 그동안 배웠던 모든 것이 한 데 모이는 시간이었습니다.
4주차에서 더티코드를 리팩터링하며 "코드를 어떻게 나눠야 하는가?"라는 물음에 답을 찾았고, 5주차에서는 "컴포넌트를 언제 분리해야 할지, 상태 관리는 어떤 기준으로 해야 할지"에 대한 체계적인 접근방법을 알게 되었으며 이번 6주차는 "코드를 어디에 두어야 하는가?"라는 새로운 고민을 마주하게 된 시간이었습니다.
항해기간의 절반을 지나니 컨디션 난조로 언제나 괴발새발했던 한 주였지만 FSD(Feature-Sliced Design) 아키텍처와 TanStack Query를 학습하고 적용하는 과정에서, 단순히 코드를 나누는 것을 넘어 "코드의 책임을 분리하는 것"의 진정한 의미를 깨달을 수 있었습니다.
과제를 통해 얻은 것들
FSD 아키텍처의 개념을 실제로 경험하다
🏗️ FSD(Feature-Sliced Design)란?
프론트엔드 프로젝트를 여러 계층(Layer)으로 나누어 관리하는 아키텍처 방법론입니다. shared → entities → features → widgets → pages 순서로 계층을 구성합니다.
커밋을 쌓아가며 계층을 구성해 나가면서 이번 과제를 통해 UI 기준(페이지별)으로만 구현하던 기존 방식에서 벗어나, FSD의 명확한 구분 기준을 어느 정도 적용할 수 있게 되었습니다. 완전한 적용은 아니지만, 이제 어느 정도 구분을 갖고 코드를 나눌 수 있는 기준을 갖게 된 것 같습니다.이제 코드를 볼 때마다 "이게 어느 계층에 속해야 할까?"를 자연스럽게 생각하게 되었습니다. 공통 컴포넌트는 shared에, 도메인 모델은 entities에, 사용자 기능은 features에, 복합 위젯은 widgets에 배치하고, 각 계층이 명확한 역할과 책임을 가지도록 구조화하는 것이 얼마나 중요한지 몸소 체감했습니다.
src/
├── pages/ # 라우트 페이지
├── widgets/ # 재사용 가능한 복합 컴포넌트
├── features/ # 사용자 기능과 이벤트 처리
├── entities/ # 도메인 엔티티 (api, types, model)
└── shared/ # 공통 유틸/컴포넌트
TanStack Query로 서버 상태 관리의 새로운 패러다임
이번 과제의 가장 큰 전환점은 TanStack Query 도입이었습니다.
기존의 useState
+ useEffect
패턴으로는 비동기 코드가 들어가고 서버와 통신을 하기 시작하니 상태관리가 엄청나게 복잡해졌습니다. 하지만 서버상태관리를 도입하니 보다 함수형 패러다임으로 선언적으로 비동기를 관리할 수 있었습니다.
// Before: useState + useEffect
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
// 복잡한 비동기 로직...
}, []);
// After: TanStack Query
const { data, isLoading } = useQuery({
queryKey: ['posts', limit, skip],
queryFn: async () => {
const [postsData, usersData] = await Promise.all([
postAPI.fetchPosts(limit, skip),
userAPI.fetchUsers(),
]);
return { posts: postsWithUsers, total: postsData.total };
},
});
가장 인상 깊었던 것은 쿼리 키를 REST API 구조와 동일하게 설계하는 것이었습니다. 처음에는 단순히 ['posts']
같은 형태로 사용했는데, 필터링이나 정렬이 추가되면서 어떻게 관리해야 할지 고민이 많았습니다. 하지만 코치님께서 "REST API 구조와 동일하게 path는 배열로, searchQuery는 Object로 관리하면 된다"고 알려주시니, 갑자기 모든 것이 명확해졌습니다.
// REST API 구조와 동일하게 설계
queryKey: ['posts'] // GET /posts
queryKey: ['posts', postId] // GET /posts/:id
queryKey: ['posts', { limit: 10, skip: 0 }] // GET /posts?limit=10&skip=0
queryKey: ['comments', postId] // GET /posts/:id/comments
낙관적 업데이트를 적용하면서 사용자 경험의 차이를 직접 체감했습니다. 서버 응답을 기다리지 않고 즉시 UI를 업데이트하니 마치 로딩이 없는 것처럼 느껴졌습니다.
export const useAddComment = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (comment: NewComment) => {
return await commentAPI.addComment(comment);
},
onSuccess: (data: any, variables: NewComment) => {
const postId = data.postId || variables.postId;
if (postId) {
const existingData = queryClient.getQueryData(['comments', postId]);
if (existingData && (existingData as any).comments) {
const updatedComments = [...(existingData as any).comments, data];
queryClient.setQueryData(['comments', postId], {
...existingData,
comments: updatedComments,
});
}
}
},
});
};
상태 관리 도구의 적절한 조합
Zustand와 TanStack Query를 함께 사용하면서 "어떤 상태를 어디서 관리해야 하는가?"에 대한 명확한 기준을 세울 수 있었습니다.
interface CommentStore {
selectedComment: Comment | null;
newComment: NewComment;
showAddCommentDialog: boolean;
showEditCommentDialog: boolean;
setSelectedComment: (comment: Comment | null) => void;
setNewComment: (comment: NewComment) => void;
clearNewComment: () => void;
}
처음에는 모든 상태를 TanStack Query에서 관리하려고 했는데, 이게 오히려 복잡해지는 경험을 했습니다. 모달 열림/닫힘 같은 UI 상태까지 서버 상태 관리 도구에서 처리하려다 보니 오버엔지니어링이 되었죠. 결국 서버에서 가져오는 데이터는 TanStack Query가, 사용자 인터랙션과 관련된 UI 상태는 Zustand가 담당하는 것이 가장 자연스럽다는 것을 깨달았습니다.
기술적 고민과 의사결정
도메인 분리 기준에 대한 고민
초반에 가장 고민했던 부분은 서로 밀접한 관계를 가진 도메인들을 통합할지, 별도로 분리할지였습니다. 예를 들어 게시물과 댓글처럼 하나를 클릭하면 다른 것을 불러오고, 하나를 추가하면 다른 것의 목록이 업데이트되는 밀접한 관계라 하나의 도메인으로 통합하는 게 나을지 고민이 많았습니다.
과제 제출 시 코치님께 질문드렸더니, "서로 밀접한 관계이나 각자의 기능적 독립성이 있는 관계이므로 엔티티는 분리가 되는 것이 맞다"는 답변을 주셨습니다. 특히 "엔티티는 interface를 기준으로 생각해보면 좋다"는 조언이 인상 깊었습니다.
이 조언을 듣고 나니 도메인 분리 기준이 명확해졌습니다. 관계의 밀접도가 아니라 각자의 기능적 독립성이 중요하다는 것을 깨달았습니다.
낙관적 업데이트의 트레이드오프
코치님께서는 "낙관적 업데이트(Optimistic Update)"의 개념을 명확히 설명해주셨습니다:
"서버의 응답을 기다리지 않고 화면의 데이터를 먼저 변경하고 적용하는 방식을 낙관적 업데이트라고 합니다."
또한 실무적인 조언도 덧붙여주셨습니다:
"낙관적 업데이트를 하면 사용자 경험이 확실히 좋아집니다. 로딩이 없는 것처럼 느낄 수 있죠. 그렇지만 그만큼 코드의 작성과 고민해야할 포인트가 늘어납니다. 사용성이 중요한 경우에는 꼭 해야하는 방식이고, 그렇지 않은 경우에는 비용에 맞춰서 진행하면 됩니다."
이 피드백을 통해 낙관적 업데이트는 단순히 "좋은 패턴"이 아니라, 비즈니스 요구사항과 개발 비용을 고려한 전략적 선택이라는 것을 깨달았습니다.
KPT 회고
Keep (계속 유지하고 싶은 점)
- 계층별 책임을 명확히 하는 FSD 구조를 프로젝트 초기부터 적용
- TanStack Query로 서버 상태를 선언적으로 관리하는 습관
- 작은 단위의 커밋을 통해 변경 이력을 명확히 기록
- 코드 리뷰와 토론을 통해 더 나은 방향성을 모색하는 자세
Problem (개선이 필요한 문제점)
- FSD의 개념 이해 미흡으로 인한 초반 시행착오
- 테스트 코드 부재로 리팩터링의 안전성을 검증할 수단이 없음
Try (앞으로 시도해 볼 점)
- 엔티티를 interface 기준으로 생각하는 습관 갖기
- 낙관적 업데이트의 onMutate/onError 패턴을 실전에서 연습
- 항해 끝나면 배운 내용 정리하며 개인 프로젝트에 적용해보기
추천인 코드
제 후기를 보고 프로그램에 관심이 생기셨다면, 감사한 마음으로 수강료 할인을 받을 수 있는 추천인 코드를 드립니다.
수강 신청 시 추천인 코드 WlHJDO 입력하면 20만 원 할인이 적용되며, 자세한 커리큘럼과 일정은 아래 링크에서 확인하실 수 있습니다.
추천인 코드 : WlHJDO
'항해99 플러스 프론트앤드 > 10주 과정' 카테고리의 다른 글
항해플러스 프론트엔드 6기 8주차 회고 (0) | 2025.10.11 |
---|---|
항해플러스 프론트엔드 6기 7주차 회고 (0) | 2025.10.09 |
항해플러스 프론트엔드 6기 5주차 회고 (0) | 2025.09.23 |
[WIL] 항해플러스 프론트엔드 6기 4주차 회고 (7) | 2025.08.04 |
[WIL] 항해플러스 프론트엔드 6기 3주차 회고 (13) | 2025.07.28 |