티스토리 뷰

이번 주차 목표 (관심사 분리와 폴더구조)
(1) 단일책임원칙이 적용된 좋은 폴더 구조를 안다.
1. 문제 (과제, 프로젝트를 진행하면서 부딪혔던 기술적인 문제)
- Jotai와 Tanstack query를 함께 사용할 때의 일관성
TanStack Query가 “요청 → 캐싱 → 로딩/에러 관리”의 역할을 해준다는 점은 이해했지만, 기존 상태 관리의 actions 역할을 어디까지 위임할 수 있는지가 애매하게 느껴졌다.
예를 들어 기존에는 addComment, updatePost 등의 상태 변경 로직을 Jotai 기반으로 직접 관리했는데, 이런 동작들을 이제는 mutation으로 옮겨야 하나? 아니면 여전히 Jotai로 관리해야 하나?
로컬 상태와 서버 상태의 책임 분리가 뚜렷하지 않아서 혼란스러웠다.
2. 시도 및 해결
- Tanstack query 구조 전환
Query는 단순 요청이 아니라 요청 + 캐싱 + 상태 처리 + 사이드 이펙트까지 포함되기 때문에, 이제는 단순 HTTP 요청만 entities에 남기고, 실제 useQuery, useMutation 기반의 데이터 흐름은 features로 올리는 게 맞다고 느꼈다.
// 개선 후 구조
entities/
└── post/
├── api/
│ └── post-api.ts ← axios/fetch 등 HTTP 요청
└── model/
├── postAtoms.ts ← Jotai atom 정의
features/
└── post/
├── api/
│ └── usePostsQuery.ts ← TanStack Query만 담당
└── model/
└── useFetchPosts.ts ← useQuery 결과 → Jotai 상태로 동기화
기존에는 entities/model에 그걸 다 넣었었는데... 어차피 Tanstack Query를 한 번 거쳐야 했기 때문에 features에 다 옮겨놓았다.
- Query 로직 (useQuery, useMutation)
- 로컬 상태 관리 로직 (Jotai)
- 순수 API 요청 (axios/fetch)
이 각각의 책임이 명확히 나뉘어 유지보수가 훨씬 쉬워졌다.
3. 개선해야 할 것
- 상태 로직이 지금 흩어져 있어서 보기가 너무 어려움
// commentAtoms.ts
export const commentsAtom = atom<Comment[]>([]);
export const selectedCommentAtom = atom<Comment | null>(null);
const [comments, setComments] = useAtom(commentsAtom);
const [selected, setSelected] = useAtom(selectedCommentAtom);
atom 정의와 상태 조작 로직을 별도로 작성했기 때문에, 상태 흐름을 추적하기가 어렵고 코드 전반이 분산되어 있는 느낌을 받았다.
// hooks/useCommentActions.ts
import { useAtom } from 'jotai';
import { commentsAtom, selectedCommentAtom } from '../model/commentAtoms';
export const useCommentActions = () => {
const [comments, setComments] = useAtom(commentsAtom);
const [selectedComment, setSelectedComment] = useAtom(selectedCommentAtom);
const selectComment = (comment: Comment) => setSelectedComment(comment);
const addComment = (newComment: Comment) => setComments(prev => [...prev, newComment]);
return {
comments,
selectedComment,
selectComment,
addComment,
};
};
useCommentActions 같은 훅으로 통합하게 되면 유지보수와 확장성에 유리하다. 도메인 책임 단위로 커스텀 훅을 구성하는 습관을 들이면 좋을 거 같다.
4. 알게된 것
- entities와 featurs의 차이
과제를 진행하면서 가장 애매했던 부분 중 하나는 ui → model → api로 구조를 분리해 나가는 과정에서 entities와 features의 경계가 모호하게 느껴졌던 점이었다.
| 기준 | entities | features |
| 역할 | "무엇인가?" (정의) | "무엇을 할 수 있나?" (기능) |
| 예시 | 상품, 유저, 주문 등 | 장바구니 추가, 좋아요 누르기, 리뷰 쓰기 |
| 재사용성 | 다른 도메인에서도 쓰임 | 주로 하나의 목적/인터랙션에 종속됨 |
FSD 아키텍처를 사용할 때, a와 c 사이의 애매한 b를 과연 두 곳 중 어느 곳에 분리해놓아야 하는 건지 감이 잡힌 거 같다.
---
Keep : 현재 만족하고 계속 유지할 부분
포기하지 않고 개선하기
목요일 늦은 밤에 과제를 마무리했지만, 개인적으로 아쉬운 부분들이 있어서 여러 번 수정을 반복했다. 큰 변화는 없었지만, 시간이 부족하다는 이유로 개선을 포기하지 않고 끝까지 코드 품질을 높이려 노력했던 점이 마음에 들었다.
Problem : 개선이 필요하다고 생각하는 문제점
무턱대고 코딩했다.
이번 과제에서는 특히 무작정 코딩부터 시작했던 것이 문제였다. 전역 상태 관리 툴로는 Jotai와 Zustand 중 무엇을 쓸지 고민했고, Vue에서는 Pinia를 써본 경험이 있었지만 React 환경은 익숙하지 않아 더 어려움을 겪었다. TanStack Query가 어떤 일을 하는지는 알고 있었지만, 전역 상태 관리와 함께 사용하는 구조가 복잡하게 느껴져 많은 시간을 들이게 됐다.
Try : 문제점을 해결하기 위해 시도해야 할 것
겁먹지 말기. 되돌아갈 수 있어도 도전해보기
학습 메이트님이 “전역 상태 관리 툴을 써본 경험이 있다면 Zustand도 금방 적응할 수 있다”고 조언해주었지만… 나는 겁쟁이였다. 고민 끝에 결국 Jotai를 선택했다.
Jotai는 atom()으로 상태를 정의하고, useAtom()으로 값을 사용하는 방식이라 기본적인 형태는 익숙했다. 실제로 useState를 atom으로 바꾸는 느낌이라 도입 자체는 어렵지 않았지만, 기존에 Vue에서 Pinia로 쓰던 방식과는 달라 처음엔 혼란스러웠다.
뭔가 통으로 묶어 관리하고 싶었는데, 초반에 구조에 대한 고민 없이 무작정 적용했던 게 오히려 코드의 일관성을 해쳤던 것 같다. 뭔가... 코드가 더러워 보인달까, 통일이 안 된 느낌이랄까. 실제로 피드백도 그렇게 받았다.
앞으로는 새로운 도구를 도입할 때 구조부터 잘 설계하고, 다양한 툴을 익숙하게 다루는 개발자가 되어보고 싶다.
'항해 플러스 프론트엔드 5기 회고' 카테고리의 다른 글
| [항해 플러스 프론트엔드 5기] 3챕터 회고(지옥이었다...) (0) | 2025.05.24 |
|---|---|
| [항해 플러스 프론트엔드 5기] 7주차 회고 (0) | 2025.05.18 |
| [항해 플러스 프론트엔드 5기] 5주차 회고 (1) | 2025.04.27 |
| [항해 플러스 프론트엔드 5기] 4주차 회고 (0) | 2025.04.18 |
| [항해 플러스 프론트엔드 5기] 1챕터 회고(솔직 후기) (1) | 2025.04.13 |
- Total
- Today
- Yesterday
- 프론트엔드개발자
- 모두를위한머신러닝딥러닝
- 딥러닝
- 가상돔
- 머신러닝
- 항해플러스5기
- 경력
- 심층신경망
- 배치정규화
- 개발자
- SungKim
- 기술면접
- 자바스크립트개념
- 항해솔직후기
- 항해플러스프론트엔드
- 5기
- 항해99
- 프론트엔드기술면접
- edwith
- 딥러닝2단계
- 최적화알고리즘
- 항해플러스후기
- 하이퍼파라미터
- 프론트엔드
- virtual dom
- 경력기술면접
- 항해플러스
- 이직
- 브라우저 렌더링
- 최적화문제
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |