[Week13] React(TypeScript) 기반의 동적 UI 개발 (10) - 04/09

Kyulee·2026년 4월 9일

TIL 

목록 보기
66/90
post-thumbnail

지난 시간에 이어 북스토어 프로젝트의 메인 화면을 구성하고 모바일 환경에 맞게 반응형을 적용했습니다.


1. 메인 화면 배너 구현

메인 화면 상단의 배너는 리액트 기본 기능을 활용해 직접 슬라이드 형태로 구현했습니다. CSS의 transform: translate() 속성과 현재 보여줄 배너 데이터의 인덱스 상태를 조합하면 이미지를 옆으로 이동시킬 수 있습니다.

const [currentIndex, setCurrentIndex] = useState(0);

const handleNext = () => {
  setCurrentIndex((prev) => (prev + 1) % banners.length);
};

// 오른쪽 버튼 클릭 시 왼쪽으로 이동하므로 음수(-)를 적용합니다
const BannerWrap = styled.div<{ $currentIndex: number }>`
  display: flex;
  transition: transform 0.4s ease;
  transform: translateX(${({ $currentIndex }) => $currentIndex * -100}%);
`;

배너를 불러올 백엔드 API가 아직 없기 때문에 MSW를 활용해 모킹 서버에서 가짜 데이터를 받아오도록 처리했습니다. 이전 시간에 MSW를 세팅해둔 덕분에 API 없이도 자연스럽게 개발을 이어갈 수 있었습니다.


2. 컴포넌트 스타일 상속 (추천 도서)

추천 도서 영역은 기존에 만들어둔 BookItem 컴포넌트를 재사용하여 구현했습니다. styled-components 를 사용하면 기존 컴포넌트의 스타일 객체를 export 하여 다른 파일에서 선택자로 접근할 수 있습니다.

이를 활용해 추천 도서 목록에서는 불필요한 요약, 가격, 좋아요 수를 숨기도록 스타일을 덮어씌웠습니다.

// BookItem.tsx — 스타일 객체를 export합니다
export const BookItemStyle = styled.div`
  /* BookItem 기본 스타일 */
`;

// BookBestItem.tsx — BookItemStyle을 선택자로 활용해 스타일을 덮어씁니다
const BookBestItemStyle = styled.div`
  ${BookItemStyle} {
    .summary,
    .price,
    .likes {
      display: none;
    }

    h2 {
      display: -webkit-box;
      -webkit-line-clamp: 2;
      -webkit-box-orient: vertical;
      overflow: hidden;
      text-overflow: ellipsis;
    }
  }
`;

function BookBestItem({ book, itemIndex }: Props) {
  return (
    <BookBestItemStyle>
      <BookItem book={book} view="grid" />
      <div className="rank">{itemIndex + 1}</div>
    </BookBestItemStyle>
  );
}

컴포넌트를 새로 만들지 않고 기존 컴포넌트를 재사용하면서 필요한 부분만 스타일로 제어할 수 있어 코드 중복을 줄이고 유지보수도 쉬워집니다.


3. 캐러셀 라이브러리 활용 (리뷰 목록)

배너에서는 슬라이더를 직접 구현했다면, 리뷰 목록은 react-slickslick-carousel 라이브러리를 활용해 캐러셀(Carousel) 형태로 구현했습니다.

npm install react-slick slick-carousel
npm install -D @types/react-slick

캐러셀은 하나의 한정된 영역 안에 여러 개의 콘텐츠를 교차해서 표시할 수 있는 UI 컴포넌트입니다. 검증된 라이브러리를 사용하면 스와이프 기능이나 무한 반복 등 복잡한 슬라이더 로직을 쉽게 처리할 수 있습니다.

import Slider from 'react-slick';
import 'slick-carousel/slick/slick.css';
import 'slick-carousel/slick/slick-theme.css';

const settings = {
  dots: true,
  infinite: true,
  speed: 500,
  slidesToShow: 3,
  slidesToScroll: 1,
  responsive: [
    {
      breakpoint: 768,
      settings: { slidesToShow: 1 },
    },
  ],
};

function ReviewCarousel({ reviews }: Props) {
  return (
    <Slider {...settings}>
      {reviews.map((review) => (
        <ReviewItem key={review.id} review={review} />
      ))}
    </Slider>
  );
}

직접 구현과 라이브러리 활용 두 가지 방식을 모두 경험해보니, 간단한 슬라이더는 직접 구현하고 복잡한 인터랙션이 필요한 경우에는 라이브러리를 활용하는 것이 효율적이라는 것을 느꼈습니다.


4. 모바일 대응 (반응형 웹)

다양한 기기 크기에 맞춰 최적화된 화면을 제공하기 위해 반응형 웹 디자인을 적용했습니다. 반응형 구현을 위해 고려해야 할 세 가지 주요 요소가 있습니다.

요소설명
뷰포트(Viewport)meta 태그로 모바일 기기에서 화면 배율을 제어합니다
상대 길이 단위px 대신 %, vw, vh 등 화면 크기에 따라 유연하게 변하는 단위를 사용합니다
미디어 쿼리(Media Query)특정 화면 너비의 분기점(Breakpoint)을 감지해 레이아웃을 다르게 적용합니다
// theme.ts — 브레이크포인트를 테마에서 관리합니다
export const mediaQuery = (maxWidth: number) =>
  `@media (max-width: ${maxWidth}px)`;

export const media = {
  mobile: mediaQuery(768),
  tablet: mediaQuery(1024),
};
// 컴포넌트에서 사용
const LayoutStyle = styled.div`
  max-width: 1020px;
  padding: 0 20px;

  ${media.mobile} {
    padding: 0 12px;
  }
`;

inputMode 속성으로 UX 개선

폼을 구성할 때 input 태그의 inputMode 속성을 명시하면, 모바일 브라우저에서 입력 필드 성격에 맞는 가상 키보드를 띄워줘 사용자 경험을 크게 개선할 수 있습니다.

{/* 숫자 패드 */}
<input type="text" inputMode="numeric" placeholder="수량 입력" />

{/* 이메일 키보드 */}
<input type="text" inputMode="email" placeholder="이메일 입력" />

작은 속성 하나지만 모바일 사용자 입장에서는 체감 편의성이 크게 달라지는 부분입니다.

profile
안녕하세요 매일의 배움을 기록으로 자산화하는 개발자 이규현입니다 😊

0개의 댓글