이 글은 노마드코더 - AI 기초 탄탄 클럽 시리즈의 13번째 기록입니다. (총 13개)

노마드코더에서 수강 가능한 풀스택 웹 기초를 6주만에 훑는 챌린지. 해당 강의: ReactJS로 영화 웹 서비스 만들기 #22는 React(JS)로 Marvel Character Gallery 만들기


챌린지 목표

  • Marvel API, React, 그리고 React Router를 사용하여 아래와 같은 2개의 페이지가 있는 앱을 만드세요.
  • Home Page (/): 마블 캐릭터의 리스트를 보여주세요.
  • Details Page (/character/:id): 각 캐릭터의 디테일 페이지를 보여주세요.
  • 마블 캐릭터 리스트를 받을 수 있는 API : https://marvel-proxy.nomadcoders.workers.dev/v1/public/characters?limit=50&orderBy=modified&series=24229,1058,2023
  • 각 캐릭터의 디테일을 받을 수 있는 API : https://marvel-proxy.nomadcoders.workers.dev/v1/public/characters/:id (replace :id for a real number ID)

챌린지 조건

  • 블루프린트에는 React 그리고 React Router 가 설치되어있습니다.
  • useEffect, fetch, Link 그리고 useParams 사용하세요.

동적 데이터 정렬 (Data Sorting)

  • API가 정렬을 지원하지 않을 때 프론트엔드에서 직접 정렬하는 법.
  • Array.sort()String.localeCompare()를 사용하여 문자열을 알파벳 순으로 정렬. sort()는 원본 배열을 직접 변경(in-place)하므로 새 변수에 할당하지 않아도 됨.
const sortedCharacters = json.data.results.sort((a, b) =>
  a.name.localeCompare(b.name)
);
setCharacters(sortedCharacters);

리액트 라우터와 동적 경로 (Dynamic Routing)

  • URL 파라미터를 활용하여 상세 페이지 구현.
  • useParams 훅으로 주소창의 :id 값을 추출하고, 이를 기반으로 상세 API 호출. useEffect의 dependency array에 [id]를 넣어야 id가 바뀔 때 재요청됨.
  • 동일한 Detail.jsx 컴포넌트 하나로 모든 캐릭터의 상세 페이지 처리 가능.
const { id } = useParams();

useEffect(() => {
  const fetchDetail = async () => {
    const response = await fetch(
      `https://marvel-proxy.nomadcoders.workers.dev/v1/public/characters/${id}`
    );
    const json = await response.json();
    setCharacter(json.data.results[0]);
    setIsLoading(false);
  };
  fetchDetail();
}, [id]);

CSS 호버 인터랙션 (카드 뒤집기 효과)

진짜 3D flip (카드 전체가 회전)이 아니라, 세 가지 효과를 조합한 방식:

  1. 이미지만 rotateY(180deg) 회전 → 뒤집히는 느낌 연출
  2. 카드 앞면 filter: brightness(0.2) → 어둡게 페이드
  3. 카드 뒷면 opacity: 0 → 1 전환 → 텍스트 fade in

perspective, transform-style: preserve-3d, backface-visibility: hidden은 3D 공간 컨텍스트를 만들어주는 역할.

  • hover를 부모(.card-container)에 걸어두면, 자식 요소들(.card-front, .card-back, .character-img)을 각각 개별 제어할 수 있어서 유연함.
.card-container:hover .character-img {
  transform: rotateY(180deg);
}
.card-container:hover .card-front {
  filter: brightness(0.2);
}
.card-container:hover .card-back {
  opacity: 1;
  transition: opacity 1s;
}
  • 처음에 opacity: 100으로 작성했는데, opacity의 유효 범위는 0~1. 브라우저가 1로 clamp 해줘서 동작은 했지만 비표준. opacity: 1로 수정 함.

레이아웃 안정화 (Layout Stability)

  • .card-front.card-backposition: absolute로 겹쳐 쌓을 때, display: flex + align-items: center + justify-content: center로 내부 요소 정렬.
  • box-sizing: border-box 전역 적용으로 padding 포함 너비 계산 혼선 방지.
* {
  box-sizing: border-box;
}

.card-front,
.card-back {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  padding: 10px;
}


Series: 노마드코더 - AI 기초 탄탄 클럽