[7팀 김민규] Chapter 2-3. 관심사 분리와 폴더구조#65
Conversation
| export const COMMENT_QUERIES = { | ||
| all: ["comments"] as const, | ||
| byPostId: (postId: number) => [...COMMENT_QUERIES.all, "post", postId] as const, | ||
| } |
There was a problem hiding this comment.
tanstack-query 공식문서에서 추천하는 query key factory 라이브러리도 한 번 추천드려봅니다.
외관적으로는 달라지는 부분은 크게 없지만, 개인적으로는 타이핑이 강화되고 자동완성이 지원돼서 사용할 때 더 안정감이 생기는 정도(?) 입니다 ㅎㅎ.
| return useMutation({ | ||
| mutationFn: (comment: INewComment) => commentApi.createComment(comment), | ||
| onSuccess: (_, variables) => { | ||
| queryClient.invalidateQueries({ queryKey: COMMENT_QUERIES.byPostId(variables.postId) }) |
There was a problem hiding this comment.
invalidate을 통해 새로 호출하는 것으로 이게 되는거였나? 하는 생각이 들어 확인해봤는데 msw api를 통해서 서버 작업을 구현하셨군요. 멋집니다 👍
| setShowAddCommentDialog: (showAddCommentDialog: boolean) => void | ||
| setShowEditCommentDialog: (showEditCommentDialog: boolean) => void | ||
| setSelectedComment: (selectedComment: IComment | null) => void | ||
| } |
There was a problem hiding this comment.
하나로 관리하는게 코드 중복도 줄이고 가독성도 좋아보이네요. 배워갑니다 👍
| onClick={() => { | ||
| updateComment.mutate(selectedComment) | ||
| setShowEditCommentDialog(false) | ||
| setSelectedComment(null) |
There was a problem hiding this comment.
요기의 일련의 동작도 useCommentEditForm hook 에서 조립해서 export 하는 건 어떻게 생각하시나요? interaction과 관련된 event handler는 의도적으로 따로 관리하시는건지 궁금해서 여쭤봅니다 ㅎㅎ.
| title: "", | ||
| body: "", | ||
| userId: 1, | ||
| }) |
There was a problem hiding this comment.
작성중인 post 내용은 local state로 분리하는 꼼꼼함...! 성능에도 신경을 쓰셨군요 👍
| const setLimit = usePostStore((state) => state.setLimit) | ||
| const setSkip = usePostStore((state) => state.setSkip) | ||
| const setTotal = usePostStore((state) => state.setTotal) | ||
| const setSelectedPost = usePostStore((state) => state.setSelectedPost) |
There was a problem hiding this comment.
저는 사실 처음에 아래처럼 사용하다가 무한 렌더링의 늪에 빠져서...
const { ... } = usePostStore(state => ({
limit: state.limit,
skip: state.skip,
...
}))
useShallow 라는 memoizedSelector 라는걸 알게 됐습니다.
| export { default as PostDetailModal } from "./PostDetailModal" | ||
| export { default as PostCreateModal } from "./PostCreateModal" | ||
| export { default as PostManagerTitle } from "./PostManagerTitle" | ||
| export { default as PostEditModal } from "./PostEditModal" |
There was a problem hiding this comment.
저도 개인적인 convention으로 index.ts 를 통해서 export할 함수나 컴포넌트들을 전부 관리하는데, 모든 파일에대해서 named export 로 통일하니까 index.ts 에서는 늘 export * from "/..." 로 줄일 수 있어서 편한 것 같더라구요 ㅎㅎ.
| return <button className={buttonVariants({ variant, size, className })} ref={ref} {...props} /> | ||
| }) | ||
|
|
||
| Button.displayName = "Button" |
| "baseUrl": "src", | ||
| "paths": { | ||
| "@/*": ["*"] | ||
| }, |
There was a problem hiding this comment.
작은 차이지만, import 문 가독성과 편의성에서 확연한 차이가 만들어지네요. 참고하겠습니다.
jotace06
left a comment
There was a problem hiding this comment.
고생하셨습니다!
전반적으로 코드가 깔끔하고 응집도 좋게 나뉘어 있어서 쉽게 파악할 수 있었어요. 다른 사람에게 잘 읽히는 코드를 작성하기 위해 많은 노력을 기울여주셨다는게 느껴졌습니다. 민규님 코드를 보면서 앗 나도 이렇게 할 걸! 하는 부분을 여러곳 발견하게 됐어요. 특히 msw로 유사 서버기능을 구현해서 dummyjson api의 부족한 부분까지 채워주신 점도 너무 멋지네요. 잘 읽고 공부해 갑니다. 👍
| <UserModal /> | ||
| <PostCreateModal /> | ||
| <PostEditModal /> | ||
| <PostDetailModal /> | ||
| <CommentCreateModal /> |
There was a problem hiding this comment.
오 보통 pages에 많이 위치시키던데 모달들을 widgets에 위치시키셨군요! widgets에 위치시킨 이유가 궁금합니닷
There was a problem hiding this comment.
그저... 테오가 모달은 위젯이다 라는 말을 어디서 주워들었습니다.
| import { usePostModalStore } from "@/features/post/model/store" | ||
|
|
||
| export const usePostModal = () => { | ||
| const showPostModal = usePostModalStore((state) => state.showPostModal) | ||
| const setShowPostModal = usePostModalStore((state) => state.setShowPostModal) | ||
|
|
||
| return { showPostModal, setShowPostModal } | ||
| } |
There was a problem hiding this comment.
Modal 상태를 전역적으로 관리하면서 또 커스텀 훅을 만드신 이유가 있나용?
jinsoul75
left a comment
There was a problem hiding this comment.
시간없었다면서!! 역시 해내는 민규님!!!!! 짱짱!!!!!! 정글 가지마요 흑흑흑흑흑흐긓그흑흑흐긓그흑 못보내!!!
| async function main() { | ||
| await worker.start({ | ||
| serviceWorker: { | ||
| url: "/front_5th_chapter2-3/mockServiceWorker.js", | ||
| }, | ||
| }) | ||
|
|
||
| ReactDOM.createRoot(document.getElementById("root")!).render( | ||
| <StrictMode> | ||
| <QueryProvider> | ||
| <App /> | ||
| </QueryProvider> | ||
| </StrictMode>, | ||
| ) | ||
| } |
There was a problem hiding this comment.
이제 ES2022 버전부터는 Top-level await가 지원되어서, 더 이상 async function main() { ... }과 같이 await 코드를 main 함수로 래핑하지 않아도 괜찮답니다!
There was a problem hiding this comment.
아앗..! 그렇군요!! AI를 사용했더니.. 이런 부작용이...ㅠㅠ
| export const Button = forwardRef<HTMLButtonElement, IButtonProps>(({ className, variant, size, ...props }, ref) => { | ||
| return <button className={buttonVariants({ variant, size, className })} ref={ref} {...props} /> | ||
| }) |
There was a problem hiding this comment.
이번 과제의 React 버전이 19라서, 몇몇 경우에는 더 이상 forwardRef를 명시적으로 사용하지 않아도 된다고 합니다! 코치님께서 이번 업데이트를 의도하신 것은 아니라고 말씀하셨지만, 이 점을 참고하시면 코드를 조금 더 간결하게 작성할 수 있는 부분이 있을 것 같아요.
There was a problem hiding this comment.
아~~ 그렇구나... 사실 저는 forwardRef가 뭔지 잘 몰라서 공부해볼 필요가 있을 것 같아요!
There was a problem hiding this comment.
토스에서 useOverlay라는 Overlay를 선언적으로 관리하기 위한 유틸 훅을 만들었어요! 저는 zustand와 스까스까서 활용하고 있답니다. 토스는 Overlay를 어떻게 관리하는지 한번 보시면 좋으실것 같아서 남겨놓습니다!
keyonnaise
left a comment
There was a problem hiding this comment.
민규님, 크래프톤 정글 과정으로 매우 바쁘실 텐데도 과제를 훌륭하게 완료하신 것을 보니 저도 정말 기쁩니다. 크래프톤 정글 과정 또한 훌륭히 마치실 수 있을 거라고 믿습니다! 항상 응원하겠습니다.
과제 체크포인트
https://suinkimme.github.io/front_5th_chapter2-3/
기본과제
목표 : 전역상태관리를 이용한 적절한 분리와 계층에 대한 이해를 통한 FSD 폴더 구조 적용하기
체크포인트
심화과제
목표: 서버상태관리 도구인 TanstackQuery를 이용하여 비동기코드를 선언적인 함수형 프로그래밍으로 작성하기
체크포인트
과제 셀프회고
과제에서 좋았던 부분
React 프로젝트를 진행하면서 FSD(Feature-Sliced Design) 관점에서 애플리케이션 구조를 바라보게 된 점이 인상 깊었습니다. 단순히 컴포넌트를 나열하는 수준을 넘어서, 기능 단위로 책임을 분리하고 각 계층의 역할을 명확히 하려는 시도가 개발 전반에 걸쳐 큰 영향을 주었습니다.
처음에는 구조를 강제하는 것이 오히려 개발 속도를 저하시킬 수 있지 않을까 우려도 있었지만, 오히려 반대로 기능이 복잡해질수록 이러한 설계적 사고가 코드의 유지보수성과 확장성을 높이는 데 기여한다는 점을 체감할 수 있었습니다. 특히, entities, features, shared, widgets, pages 등 각 층위에서 어떤 책임을 가져가야 하는지 고민하면서 자연스럽게 "이 기능은 어디에 있어야 맞는가?"라는 질문을 습관화할 수 있었습니다.
또한 이 과정에서 디렉터리 구조를 의미 있게 설계하는 법, 컴포넌트 간의 결합도를 낮추는 방법, 단방향 데이터 흐름을 유지하면서도 기능을 나누는 방법 등에 대해 실제적인 감을 잡을 수 있었던 것도 큰 수확이었습니다.
이번 프로젝트를 계기로 단순히 돌아가는 코드가 아니라, 잘 구조화된 코드, 앞으로도 손을 대기 쉬운 코드를 만들기 위해 어떤 기준이 필요한지를 더 깊이 고민하게 되었습니다. 앞으로의 프로젝트에서도 이러한 설계 관점을 적극적으로 적용해보고 싶다는 생각이 들었습니다.
과제를 하면서 새롭게 알게된 점
이번 과제를 하면서 기술적인 내용보다 제 습관이나 태도에서 부족한 점을 더 많이 느꼈습니다. 특히 정리 정돈이 잘 안 된다는 점이 계속 드러났습니다. 기능을 구현하려고 할 때, 미리 정리해 둔 게 없다 보니 혼자 헤매는 시간이 많았습니다.
처음부터 계획 없이 시작했던 탓에 구조를 바꾸거나 코드를 다시 손보는 데 불필요한 시간이 들어갔습니다. 그동안 ‘일단 되게 만들자’는 식으로 넘겼던 습관들이 결과적으로는 비효율로 이어졌다는 걸 깨달았습니다.
앞으로는 개발을 시작하기 전에 흐름이나 구조를 미리 정리하고, 작업 중간에도 계속 기록을 남기는 습관이 필요하겠다고 느꼈습니다. 그래야 같은 문제를 반복하지 않을 수 있을 것 같습니다.
과제를 진행하면서 아직 애매하게 잘 모르겠다 하는 점, 혹은 뭔가 잘 안되서 아쉬운 것들
리뷰 받고 싶은 내용이나 궁금한 것에 대한 질문