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에서 필드 추가 대비.