heojungseok 님의 상세페이지[5팀 허정석] Chapter 2-3. 관심사 분리와 폴더구조🧦

과제 체크포인트

PR 작성 중... 만약 보셨다면 다시 찾아와주세요.. 꼭 채워 넣을게요.. 믿어주세요. 저를 포기하지 말아주세요. PR 작성완료 저를 믿어주셔서 감사합니다.

배포_링크

기본과제

목표 : 전역상태관리를 이용한 적절한 분리와 계층에 대한 이해를 통한 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가 정상적으로 작동하는가?

최종과제

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

과제 셀프회고

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

"상태를 쪼개면 엔티티가 형성된다"

리팩토링 전의 과제는 상태 중심 기반으로 코드가 만들어져 있었습니다. 그래서 어떻게 FSD 구조로 효율적으로 바꿀 수 있을까? 라고 생각했고 6기 수강생 분들은 어떻게 하고 있는지 힌트를 얻기 위해 물어봤다. (goo-ccle 🧦 창립 구린내클럽) 3팀 준형이 "상태를 쪼개면 엔티티가 형성된다" 라는 개념을 얘기했다. 전혀 이해가 안돼서 바로 실전에 적용을 해봤습니다.

  1. 상태 그룹화 → 관련된 상태들을 하나로 모으기 (관심사의 분리)
  2. 도메인 인식 → "아! 이건 게시물 도메인이구나!" (도메인 경계의 명확화)
  3. 엔티티 형성 → Post, Comment, User, Tag 등이 명확하게 구분됨

엔티티는 비즈니스 도메인에서 독립적으로 존재하고 식별 가능한 객체로, 상태를 논리적으로 그룹핑하면 자연스럽게 도메인 경계가 드러나고 엔티티가 형성됩니다.

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

src/
├── app/          # 애플리케이션 설정 및 진입점
├── pages/        # 페이지 컴포넌트
├── widgets/      # 복합 UI 컴포넌트
├── features/     # 비즈니스 로직
├── entities/     # 도메인 모델
└── shared/       # 공통 유틸리티

FSD 구조의 개념을 처음 접하고 바로 실제 코드로 활용하는 것에 초점을 뒀고, 계층 구조와 각 레이어의 단방향 의존성을 준수하기 위해 노력했습니다. 또한 Import alias 를 통한 구조화를 조금 더 깔끔하게 가져갔습니다. 그리고 ESLint 설정을 통해 FSD 아키텍처의 단방향 의존성 규칙을 강제하고, 순환 참조를 방지했습니다.

ESLint
import js from "@eslint/js"
import globals from "globals"
import reactHooks from "eslint-plugin-react-hooks"
import reactRefresh from "eslint-plugin-react-refresh"
import tseslint from "typescript-eslint"
import perfectionist from "eslint-plugin-perfectionist"
import boundaries from "eslint-plugin-boundaries"
import eslintPluginImport from "eslint-plugin-import"

export default tseslint.config(
  { ignores: ["dist"] },
  {
    extends: [js.configs.recommended, ...tseslint.configs.recommended],
    files: ["**/*.{ts,tsx}"],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
    },
    plugins: {
      "react-hooks": reactHooks,
      "react-refresh": reactRefresh,
      "boundaries": boundaries,
      "import": eslintPluginImport,
      "perfectionist": perfectionist,
    },
    settings: {
      "boundaries/elements": [
        { type: "app", pattern: "src/app" },
        { type: "pages", pattern: "src/pages" },
        { type: "widgets", pattern: "src/widgets" },
        { type: "features", pattern: "src/features" },
        { type: "entities", pattern: "src/entities" },
        { type: "shared", pattern: "src/shared" },
      ],
    },
    rules: {
      ...reactHooks.configs.recommended.rules,
      "react-refresh/only-export-components": ["warn", { allowConstantExport: true }],

      // FSD 경계 룰
      "boundaries/element-types": [
        "error",
        {
          default: "disallow",
          rules: [
            { from: ["app"], allow: ["pages", "widgets", "features", "entities", "shared"] },
            { from: ["pages"], allow: ["widgets", "features", "entities", "shared"] },
            { from: ["widgets"], allow: ["features", "entities", "shared"] },
            { from: ["features"], allow: ["entities", "shared"] },
            { from: ["entities"], allow: ["shared"] },
            { from: ["shared"], allow: ["shared"] },
          ],
        },
      ],

      // 코드 포맷팅 룰
      "perfectionist/sort-imports": [
        "error",
        {
          type: "natural",
          order: "asc",
          groups: [
            "builtin",
            "external",
            "internal-type",
            "internal",
            ["parent-type", "sibling-type", "index-type"],
            ["parent", "sibling", "index"],
            "object",
            "unknown",
          ],
          customGroups: {
            value: {
              "react": ["react", "react-*"],
              "@app": "@app/.*",
              "@pages": "@pages/.*",
              "@widgets": "@widgets/.*",
              "@features": "@features/.*",
              "@entities": "@entities/.*",
              "@shared": "@shared/.*",
            },
          },
          newlinesBetween: "always",
        },
      ],
      "perfectionist/sort-named-imports": ["error", { type: "natural" }],
      "perfectionist/sort-exports": ["error", { type: "natural" }],
      "perfectionist/sort-object-types": ["error", { type: "natural" }],
      "perfectionist/sort-interfaces": ["error", { type: "natural" }],
      "perfectionist/sort-jsx-props": ["error", { type: "natural" }],
    },
  },
)


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

React Query의 진정한 의미

현업에서 리액트를 사용하지 않기 때문에 그저 단순히 쿼리문을 사용하나? 어떤거지? API 호출을 위한 라이브러리? 정도로만 알고 있었습니다. 정말 전혀 몰랐던 라이브러리였습니다. 실제 과제에서의 적용을 단순히 캐시 무효화만 구현한 것 밖에 없어서 React Query 를 너무나도 활용하지 못한 것이 아쉬워서 부족한 부분을 AI를 통해 추출하여 관련 내용을 작성해봤습니다.

useQuery와 useMutation의 차이점과 각각의 역할 📋 요약 비교표

특징useQueryuseMutation
데이터 흐름서버 → 클라이언트클라이언트 → 서버
실행 방식자동 실행수동 실행 (mutate() 호출)
캐싱자동 캐싱캐싱하지 않음
재실행자동 재실행수동 재실행만
사용 목적데이터 조회데이터 생성/수정/삭제
반환 데이터서버 데이터변경 결과
에러 처리자동 재시도수동 에러 처리
낙관적 업데이트불가능가능 (onMutate)
캐시 무효화자동 동기화성공 후 수동 무효화

💡 핵심 포인트

  • useQuery: "데이터를 가져와서 보여주기" - 자동화된 데이터 관리 (읽기 전용)
  • useMutation: "데이터를 변경하고 결과 처리하기" - 사용자 액션 기반 데이터 변경 (쓰기 전용)
  • 상호 보완: 조회와 변경을 조합하여 완전한 데이터 관리 시스템 구축 (변경된 데이터가 자동으로 화면에 반영)

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

이번 챕터를 통해 아키텍처 설계의 중요성과 점진적 개선의 이점을 체험할 수 있었습니다. 특히 "상태를 나누면 엔티티가 보인다"는 인사이트는 코드 리팩토링을 넘어서 아키텍처 설계의 본질을 이해하는데 적용해도 좋다고 생각합니다. 현재 2챕터의 프로젝트에 적용된 것은 "형식은 갖추었지만 극히 일부의 실질적인 이점을 활용한 레벨" 이지만, 앞으로의 학습과 확장을 통해 실제 프로덕션 환경에서 활용해 보고싶습니다.

챕터 셀프회고

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

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

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

처음 클린코드 과제를 접하기 전에는 마냥 재밌을거라고 생각했는데 막상 더티코드를 접하니까 답답함을 많이 느꼈습니다. 말도 안되는 포맷으로 작성된 코드를 보면서 막막함이 크게 다가왔습니다. 그렇게 조금씩 리팩토링을 하다보니 처음 느꼈던 답답함과 막막함이 완전히 해소 되지 않았으나, 점진적으로 나아지고 있는 모습을 보면서 '내가 미약하지만 감각적으로 불편해 하는 것을 골라내고 수정하고는 있구나' 싶었습니다. 그리고 아래는 6기 수강생분들과 '클린 코드란 무엇일까?' 라는 주제의 디스코드 채팅 중 일부 입니다. 이미지의 히스토리를 잠깐 설명하자면 채팅이 열리고 3분동안 수강생분들이 너 나 할 거 없이 많은 의견들을 내줬습니다. 그리고 제가 생각하는 클린 코드의 정의를 내렸습니다.

image

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

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

상태관리라는 것을 이번 챕터에서 처음 해봤습니다. 발제에서의 Props Drilling 에 대해서 설명을 했을 때는 와닿지 않았는데, 실제로 코드에서 방대한 양의 Props Drilling 을 접하니까 상태 관리의 중요성을 깨달았습니다. 중복된 props 전달을 없애고 중간 컴포넌트들이 props 전달하는 역할을 제거했습니다. 대신 이것도 너무 과하다면 컴포넌트의 외부 의존성을 파악하기가 여렵고 잘 못 하다간 특정 구조에 결합도를 높일 수 있습니다. 클린코드 과제를 하면서 '뭐든지 과함은 독이다' 를 깨달았습니다.

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

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

4, 5 주차를 하면서 제가 익숙하고 선호하는 폴더 구조는 '도메인 중심적 구조' 라는 것을 알게 됐습니다. 도메인 중심으로 설계를 하다보니 중복되는 기능들이나 코드들이 발견 되는 것을 확인했고 이것을 해결 하는 방법엔 어떤 것이 있을까 고민했습니다. 이 때 FSD 구조 적용이라는 과제를 하게 되어서 좋은 경험이 됐습니다. 그치만 처음 접한 개념이고 도전이라 물음표가 많이 찍힌 시간이였습니다. 여전히 지금도 물음표가 찍히고 있습니다. 그리고 완성된 구조를 봤을 때 FSD + 도메인 중심이 섞인 하이브리드 방식으로 진행 됐습니다. Tanstack Query 의 해방감은 느껴보진 못했으나 추후 과제나 업무에 적용을 해보면서 조금씩 해방해보겠습니다.


테오! 개인적으로 점진적으로 단계를 밟아가 끝에는 '네 생각을 펼쳐봐' 하는 스토리가 있는 과제 구성이 제 취향이라 마음에 들었습니다. 다만, 제 실력이 과제를 못 따라간 부분도 있어서 아쉬움도 많이 남습니다. 그래도 이 안에서 나름대로 배움이 있었다면 그걸로 만족하는 챕터로 기억하고 싶습니다. 앞으로도 '기승전결'이 있는 양질의 과제 부탁드리겠습니다. 한 챕터 동안 고생 많으셨습니다.!

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

@features/
├── posts/
│   ├── post-crud/      # 게시글 CRUD
│   ├── post-search/    # 게시글 검색
│   └── post-tags/      # 게시글 태그 관리
├── comments/
│   ├── comment-crud/   # 댓글 CRUD
│   └── comment-likes/  # 댓글 좋아요
├── users/
│   ├── user-profile/   # 사용자 프로필
│   └── user-auth/      # 사용자 인증
└── search/
    ├── global-search/  # 전체 검색
    └── search-filters/ # 검색 필터
  1. 추후 FSD를 적용한다면 위처럼 features 를 기능별 세분 방식으로로 진행 해보고싶습니다. 그래서 위 구조가 실무에서도 활용을 자주하는 구조인지? 아니면 다른 구조가 좀 더 실무에 적합한지에 대한 의견이 듣고싶고, 실질적인 예시가 있다면 간략한 구조도를 부탁드리겠습니다.

과제 피드백

가만히 있었는데 믿어준 사람이 된거같아서 너무 좋네여. 정석님 고생 많으셨습니다. 더 챙겨야 할 부분은 많이 있지만 그럼에도 전반적인 관점에서 필요한 고민들은 많이 해주신 것 같아요! 이전 과제도 그랬겠지만, 지금 과제에서도 새로운 얘기가 정말 많으셨던것 같은데 학습의 맥락에서도 하나하나 짚어서 과제를 해내주시는게 정말 멋있는것 같습니다. 아키텍처의 관점도 사실은 리액트에 국한되는 내용이 아니라 개발 전체를 관통하는 이야기의 일환이긴 하거든요. 레이어를 나누고 각 코드를 배치하고 하는 약속들을 통해 응집성을 같고 순환참조를 맺지 않게 하면서 결합도를 낮추는 방향들을 많이 보신것 같아 좋네요!

추후 FSD를 적용한다면 위처럼 features 를 기능별 세분 방식으로로 진행 해보고싶습니다. 그래서 위 구조가 실무에서도 활용을 자주하는 구조인지? 아니면 다른 구조가 좀 더 실무에 적합한지에 대한 의견이 듣고싶고, 실질적인 예시가 있다면 간략한 구조도를 부탁드리겠습니다.

넵 좋습니다. 검색의 경우에는 종류에 따라 각 도메인에 묶어서 관리할 수도 있을것 같네요. https://feature-sliced.github.io/documentation/examples 를 보면 알겠지만, 수많은 FSD가 있고 규칙을 지키지만 구조는 전부 다른것을 알 수 있을거에요. 가장 핵심이 되는 규칙을 따르는게 중요한 것이기 때문에 통일된 규칙으로 약속을 잘 만들고 지키는게 중요하지 않을까 싶습니다.

고생하셨고 다음주도 화이팅입니다!