Project Records/Project : Team Nova MJ Search

MJS 광고 캐러셀 & 실시간 검색 순위 집계 Component 설계 가이드

Frisbeen 2025. 8. 18. 21:53

1. 광고 캐러셀 (AdCarousel)

설계 의도

  • 학교/동아리/행사 홍보용 이미지를 자동 순환으로 보여주는 컴포넌트.
  • hover 시 자동 재생을 멈추어 UX를 개선.
  • 접근성(aria-label, 버튼 제어) 고려.

기술적 포인트

  • 상태 관리
    • index: 현재 활성화된 슬라이드 인덱스를 관리.
    • timerRef: setTimeout 관리용 ref, 언마운트 시 clearTimeout 보장.
    • hoverRef: 마우스 오버 여부를 추적하여 autoplay 제어.
  • useMemo
    • slides = images가 주어지면 외부 이미지, 없으면 DEFAULT_IMAGES 사용.
    • useMemo를 사용해 불필요한 재계산 방지. (단, 단순 배열이라 성능상 큰 차이는 없으므로 가독성/안정성을 위해 선택적으로 사용.)
  • 타입 지정
    • images 배열은 옵셔널, alt/href/caption도 옵셔널로 지정해 재사용성을 높임.
    • intervalMs: 자동 순환 간격을 커스터마이즈 가능.
  • type AdCarouselProps = { images?: { src: string; alt?: string; href?: string; caption?: string }[]; intervalMs?: number; };

이미지 가이드

  • 권장 비율: 16:9 (1280×720px)
  • 모바일 대응: h-56 md:h-64 → Tailwind로 반응형 높이 지정.
  • object-cover로 이미지 비율 유지.

개선 아이디어

  • 슬라이드 전환 애니메이션을 transition-opacity로 변경 가능.
  • useCallback으로 go, goTo 함수를 메모이제이션해 useEffect deps 안정성 강화.

2. 실시간 검색 순위 (RealtimeRank)

설계 의도

  • 실시간 인기 검색어 TOP N을 표시.
  • 10초마다 자동 갱신 (shuffle 기반 더미 데이터).
  • 상승/하락/NEW/— 표시로 변화 트렌드 시각화.

기술적 포인트

  • 상태 관리
    • current: 현재 순위 배열.
    • prevRef: 직전 순위를 ref로 보관 → 리렌더 없이 참조 가능.
  • useEffect
    • setInterval로 주기적 갱신.
    • prefersReducedMotion 미디어쿼리 체크 → 모션 최소화 환경에서는 애니메이션 비활성화.
  • useMemo
    • base: 최초 데이터(외부 주입 or DUMMY)를 limit만큼 slice.
    • deltas: 이전/현재 인덱스를 비교해 변화량(up/down/new/same) 계산.
    • useMemo로 매번 불필요한 map 연산 최소화.
  • 타입 지정
    • RankItem: 검색어 문자열만 관리.
    • Delta: 상태 변화 4가지 case를 Union Type으로 지정 → 타입 안정성 및 switch-case-like 패턴 구현 용이.
  • type RankItem = { keyword: string }; type Delta = 'up' | 'down' | 'new' | 'same';

디자인 가이드

  • 상단: 제목(실시간 검색 순위) + 우측 데이터 라벨.
  • 본문: 순위 번호(1~10) + 키워드 + 상태 아이콘.
  • TOP3는 색상 강조(bg-mju-primary).

개선 아이디어

  • 실제 API 연동 시 useSWR / react-query로 대체 가능.
  • delta 계산 시, 더 직관적인 diff 알고리즘 적용 가능.

공통적인 설계 철학

  • 접근성 고려: aria-label, aria-live 활용.
  • 반응형 레이아웃: Tailwind Utility Class로 모바일~데스크탑 대응.
  • 확장성: 더미 데이터를 props로 교체 가능, 다양한 데이터 소스 대응.
  • 유지보수성: 타입스크립트로 props와 상태를 엄격히 지정.

최종 정리

  • AdCarousel은 이미지 중심 UI → autoplay + 수동 제어.
  • RealtimeRank는 텍스트 기반 UI → 순위 변동 시각화.
  • 두 컴포넌트 모두 useMemo, useEffect, ref 등 React 기본 훅을 목적에 맞게 활용.
  • 타입 정의와 상태 분리는 재사용성, 안정성, 확장성을 동시에 담보.

5) 기술 설계 상세 (성능·상태·타이핑)

5.1 useMemo/useRef/useEffect 선택 근거

  • 슬라이드 배열(slides)
    • 외부 images가 자주 변하지 않는 한 렌더마다 재계산 비용이 작음  useMemo는 선택사항.
    • 자동재생 타이머의 의존성 단순화를 위해, 타이머는 setIndex 함수형 업데이트를 사용: setIndex(i => (i + 1) % length)  go 함수를 의존성에서 제거 가능.
  • 자동재생 제어
    • hoverRef: 마우스 오버 시 자동재생 일시정지. 굳이 상태(state)로 만들면 리렌더가 불필요하게 발생 → mutable ref로 처리.
    • timerRef: 현재 타이머 id 보관 및 언마운트 시 clearTimeout 보장.
  • 이전 순위 보관(prevRef)
    • 실시간 랭킹은 직전 프레임과의 비교가 핵심 → 상태가 아니라 ref에 이전 스냅샷을 저장하면, 불필요한 리렌더 없이 델타 계산 가능.
// 타이머 의존성 최소화
useEffect(() => {
  if (!isAutoPlay || hoverRef.current || length <= 1) return;
  timerRef.current = window.setTimeout(() => {
    setIndex(i => (i + 1) % length);
  }, intervalMs);
  return () => timerRef.current && window.clearTimeout(timerRef.current);
}, [index, intervalMs, length, isAutoPlay]);

5.2 상태(State) 모델링 원칙

  • 최소 상태 보유: 파생 가능 값은 상태로 두지 않는다.
    • 캐러셀: index만 상태, 나머지(슬라이드 폭, transform 등)는 렌더 시 계산.
    • 랭킹: current만 상태, prev는 ref, deltas useMemo로 파생.
  • 파생 데이터는 메모이제이션: deltas current가 바뀔 때만 재계산.
  • 엣지 케이스 가드: length <= 1이면 컨트롤/인디케이터 숨김.

5.3 키(key)와 리스트 렌더

  • 안정 키 권장: key={s.src}(파일 경로/고유 id). 단순 인덱스 키는 재정렬/삽입 시 리마운트 위험.
  • 랭킹 리스트는 key={item.keyword}로 중복 방지(업스트림 정제 권장).

5.4 접근성(A11y) 구현 포인트

  • 캐러셀 루트: aria-roledescription="carousel", aria-label.
  • 슬라이드 래퍼: aria-live="polite"로 변화를 스크린리더에 알림.
  • 인디케이터: 현재 항목에 aria-current 부여.
  • 키보드 조작: 루트에 tabIndex={0}, onKeyDown(←/→).
  • 모션 민감도: prefers-reduced-motion 감지 시 자동재생 중지.

5.5 타입 설계(TS) 의도

  • 명시적 도메인 모델
    • 광고 단위 Ad: { src: string; alt?: string; href?: string; caption?: string }
    • 랭킹 단위 RankItem: { keyword: string }
    • 변동 상태 Delta: 'up' | 'down' | 'new' | 'same' (문자열 리터럴 유니온) → 컴파일 타임 오타 방지.
  • Props 기본값과 선택 속성
    • images?, items? optional로 두고 내부 DUMMY를 기본값으로 사용 → 온보딩/스타일 개발 시 빠른 피드백 사이클.
    • 기본값은 함수 시그니처에서 디폴트 파라미터로 명시(intervalMs = 4000, limit = 10).
  • 불변성 힌트
    • 외부에서 넘기는 리스트가 변하지 않음을 가정할 땐 readonly 배열을 고려: images?: readonly Ad[].
    • 내부에서 복제 후 사용하므로(예: slice), 원본 배열 Mutation 위험이 없음.
  • 반환 타입 추론
    • useState 초기값이 타입을 유도하므로 제네릭 생략 가능. 필요 시 useState<RankItem[]>(base)처럼 명시.

5.6 성능 체크리스트

  • 렌더 당 계산을 최소화: transform, width 등은 상수 계산 또는 인라인.
  • 타이머/인터벌 언마운트 정리 필수.
  • 이미지 용량 관리(WebP, 300KB 이하 권장). Next.js면 <Image sizes>로 응답형 최적화.

5.7 테스트 포인트

  • 호버 시 자동재생 일시정지 확인.
  • 1장일 때 컨트롤/인디케이터 미노출.
  • prefers-reduced-motion 환경(맥 OS 손쉬운 사용)에서 자동재생 중지.
  • 랭킹 변동 아이콘: NEW/▲/▼/— 가 조건에 맞게 표시되는지.

5.8 확장 설계

  • 스와이프 제스처: 터치 시작/이동/종료 좌표로 좌우 전환, 드래그 거리 임계값(예: 40px).
  • 로깅 훅: useImpression/useClickTracker를 만들어 view/click 이벤트 공통 처리.
  • 애니메이션 분리: prefers-reduced-motion 고려한 motion-safe:* Tailwind 유틸 적용.
  • API 스키마 버전: /api/trends/v1/realtime → 차후 v2에서 필드 추가 대비.