Skip to content

Feature/my solution#10

Open
donghyun1998 wants to merge 19 commits intohacks-team:mainfrom
donghyun1998:feature/my-solution
Open

Feature/my solution#10
donghyun1998 wants to merge 19 commits intohacks-team:mainfrom
donghyun1998:feature/my-solution

Conversation

@donghyun1998
Copy link
Copy Markdown

@donghyun1998 donghyun1998 commented Nov 30, 2025

📝 변경 사항 요약

iteration1 완료

설계 원칙


1. 상태 관리

Context 반환 형태는 useState와 유사한 배열로

// ✅ useState와 동일한 패턴으로 예측 가능
const [currency, setCurrency] = useCurrencyContext();

// ❌ 객체 반환은 패턴이 다름
const { currency, setCurrency } = useCurrencyContext();

useState[value, setter] 튜플을 반환하듯, 커스텀 Context도 동일한 패턴을 따르면 사용처에서 예측 가능합니다.


2. Suspense & ErrorBoundary

핵심 원칙: 관심사의 수직 분리

컴포넌트가 처리해야 할 상태를 계층별로 분리합니다:

계층 담당 처리 위치
로딩 Suspense 상위
에러 ErrorBoundary 상위
빈 데이터 컴포넌트 내부

이 구조를 통해 각 컴포넌트는 자신의 성공 케이스에만 집중할 수 있습니다.
옵셔널 체이닝(?.)이 코드 전체로 전염되는 것을 방지합니다.

Compound Component 패턴

// 컴포넌트와 관련 상태 UI를 논리적으로 함께 관리
<Suspense fallback={<UserProfile.Skeleton />}>
  <ErrorBoundary FallbackComponent={UserProfile.Error}>
    <UserProfile />
  </ErrorBoundary>
</Suspense>

3. 컴포넌트 설계

단일 책임 원칙

  • 분기 담당 컴포넌트와 UI 컴포넌트를 분리
  • 한 컴포넌트가 여러 역할을 하지 않도록 구성
// 분기 담당
function RecentPurchaseSection() {
  const { data } = useSuspenseQuery(...);

  if (data.products.length === 0) {
    return <RecentPurchaseSectionEmpty />;
  }
  return <RecentPurchaseSectionList />;
}

// UI 담당 (각자 한 가지 케이스만)
function RecentPurchaseSectionEmpty() { ... }
function RecentPurchaseSectionList() { ... }

JSX 내 조건문 최소화

  • JSX 안에 삼항연산자 사용 지양
  • 컴포넌트 레벨에서 early return으로 분기

4. SSOT (Single Source of Truth)

  • 단 한곳에서 변경시 파생된 다른 요소들에게 영향이 가 휴먼에러를 방지하도록 구성
  • value -> type 변환만 가능한 만큼 소스 데이터는 객체 배열로 구성
// coreBusiness/grade.ts
export const GRADE_CONFIG = {
  EXPLORER: { label: 'Explorer' },
  PILOT: { label: 'Pilot' },
  COMMANDER: { label: 'Commander' },
} as const;

// 타입은 Config에서 추출
export type GradeType = keyof typeof GRADE_CONFIG;

// 매퍼 함수
export function getGradeLabel(grade: GradeType): string {
  return GRADE_CONFIG[grade].label;
}

장점

  • 새 항목 추가 시 Config 한 곳만 수정
  • 타입 안전성 자동 보장

주의

  • base config 데이터끼리 계층 역전 시 사이드이펙트 유발
  • 원천 데이터끼리의 계층화 필요시 하위 폴더 생성 등으로 명시적 분리 필요

5. 이정표 vs 상수

구분 기준

구분 처리 예시
UI 이정표 JSX에 직접 작성 "최근 구매한 상품"
데이터 변환 SSOT Config EXPLORERExplorer

UI에서 "여기가 어디인지" 알려주는 텍스트는 그 자리에 있어야 가독성이 높아집니다. 모든 스트링 리터럴이 매직넘버가 아닙니다


6. 함수 설계

순수 함수로 로직 분리

  • 계산 로직은 순수 함수로 추출
  • 단일 책임 원칙 준수
  • 테스트 용이성 확보

7. Query Key Factory

// 도메인별 분리
export const userQueries = createQueryKeys('user', {
  me: {
    queryKey: null,
    queryFn: () => http.get('/api/me'),
  },
});

// 통합
export const queries = mergeQueryKeys(userQueries, gradeQueries, ...);

// 사용
useSuspenseQuery(queries.user.me);
queryClient.invalidateQueries({ queryKey: queries.user.me.queryKey });

원칙

  • queryKeyqueryFn을 함께 정의
  • 책임이 다르면 파일 분리
  • API 응답 타입은 해당 query 파일에 정의

현재 구조

현재 API들은 각각 독립적인 책임을 가지므로 별도 파일로 분리:

파일 책임
user.ts 사용자 정보
grade.ts 등급 포인트 정보
recentProduct.ts 최근 구매 상품
exchangeRate.ts 환율 정보

책임이 겹치거나 확장 시 연관성이 생기면 통합을 고려합니다.


8. 에러 처리

레벨 방식
글로벌 router.tsxerrorElement
섹션별 ErrorBoundary + .Error 컴포넌트
// react-router 네이티브 방식
{
  path: '/',
  element: <PageLayout />,
  errorElement: <GlobalErrorPage />,
}

9. 폴더 구조

src/
├── coreBusiness/     # SSOT Config
├── queries/          # React Query + API 타입
├── providers/        # Context Provider
├── utils/            # 글로벌 유틸리티
├── types/            # 공통 타입
├── components/       # 공통 컴포넌트
└── pages/
    └── [Page]/
        ├── components/
        └── utils/

iter03

1. CartProvider 액션 네이밍

  • addItem / removeItemincreaseQuantity / decreaseQuantity (수량 ±1)
  • deleteItemremoveFromCart (완전 삭제)
  • 이유: 수량 조절과 삭제 동작의 명확한 구분

2. ProductInfoSection ↔ CartControlSection 분리

  • ProductInfoSection: 상품 정보 표시만 (Tag, name, rating, price)
  • CartControlSection: 장바구니 인터랙션만
  • 이유: 단일 관심사

3. CartControls 컴파운드 패턴

<CartControls.Add />    // 장바구니에 없을 때
<CartControls.Remove /> // 장바구니에 있을 때

4. useLocalQuantity 훅 분리

  • 로컬 수량 상태 관리를 훅으로 추출
  • 이유: CartControls.Add가 로컬 수량만 관심갖도록

5. RecommendationSection 데이터 로직 분리

  • useRecommendedProducts 훅으로 쿼리 + 계산 로직 추출
  • useSuspenseQueries로 병렬 요청
  • 이유: 컴포넌트는 UI 렌더링만

🤝 리뷰어에게

SSOT 요소를 coreBusiness 폴더로 분리했습니다
해당 컨벤션에 대해 어떻게 생각하시는지 궁금합니다!
레퍼런스: toss-fe-interview/frontend-fundamentals-mock-exam-1#80

- user, grade, exchangeRate, recentProduct 쿼리 분리
- queryKey와 queryFn 함께 정의하여 응집도 향상
- CurrencyProvider로 전역 통화 상태 관리
- QueryClient Suspense 기본 옵션 설정
- Header에서 useCurrencyContext로 통화 토글 연동
- PageLayout에서 Outlet을 CurrencyProvider, Suspense로 감싸기
- CurrentLevelSection 등급 진행률 계산 로직
- RecentPurchaseSection 통화 포맷팅
- 등급 계산 순수함수 utils로 추출
- Compound Components 패턴 적용 (.Skeleton, .Error)
- router에 errorElement로 전역 에러 처리
- 통화 토글 테스트 수정 (클릭 전/후 분리 assertion)
- HTMLElement.textContent 정규식 매칭 수정
- 테스트 수정 내역 문서화
@donghyun1998 donghyun1998 changed the title Feature/my solution Feature/my solution 01 - 02 Dec 14, 2025
@donghyun1998 donghyun1998 changed the title Feature/my solution 01 - 02 Feature/my solution Dec 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant