[Week12] React(TypeScript) 기반의 동적 UI 개발 (6) - 04/03

Kyulee·2026년 4월 2일

TIL 

목록 보기
62/90
post-thumbnail

지난 시간에 이어 북스토어 프로젝트를 계속 진행하겠습니다. 이번 시간에는 도서 상세 페이지의 세부 기능들을 구현하는 과정을 정리했습니다.


1. 상세 내용 표시와 날짜 포맷

도서 상세 페이지는 서버에서 받아온 상품 정보를 화면에 렌더링하는 역할을 합니다. 이때 단순히 화면을 구성하는 것을 넘어, 데이터 포맷팅 로직을 재사용할 수 있도록 분리하는 작업이 중요합니다.

날짜 포맷 함수를 직접 구현해도 좋지만, dayjs 같은 라이브러리를 활용하면 날짜 변환부터 다양한 유틸리티 함수를 훨씬 간편하게 사용할 수 있습니다.

npm install dayjs
// src/utils/format.ts
import dayjs from 'dayjs';

export const formatNumber = (number: number) => {
  return number.toLocaleString();
};

export const formatDate = (date: string) => {
  return dayjs(date).format('YYYY년 MM월 DD일');
};

포맷 함수를 utils 폴더에 모아두면 여러 컴포넌트에서 동일한 로직을 중복 작성하지 않아도 됩니다. 날짜나 숫자 표시 방식이 바뀌어도 한 파일만 수정하면 전체에 반영됩니다.


2. 낙관적 업데이트 (Optimistic Updates)

좋아요 기능이나 장바구니 추가 같은 기능은 크게 두 가지 방식으로 구현할 수 있습니다.

방식설명
일반 업데이트서버에 요청을 보내고 응답을 받은 후 화면을 다시 그립니다
낙관적 업데이트요청과 동시에 성공을 가정하고 화면을 먼저 변경합니다

서버 응답이 조금이라도 지연되면 사용자는 답답함을 느낄 수 있습니다. 따라서 즉각적인 반응이 필요한 곳에서는 낙관적 업데이트를 사용하는 것이 사용자 경험(UX) 측면에서 훨씬 유리합니다.

const [liked, setLiked] = useState(false);

const handleLike = async () => {
  // 서버 응답을 기다리지 않고 화면을 먼저 변경합니다
  setLiked((prev) => !prev);

  try {
    await toggleLikeAPI(bookId);
  } catch (error) {
    // 요청이 실패하면 원래 상태로 되돌립니다
    setLiked((prev) => !prev);
  }
};

요청이 실패했을 때 원래 상태로 되돌리는 롤백 처리도 함께 구현해야 합니다. 낙관적 업데이트는 성공 확률이 높은 작업에 적용할 때 가장 효과적입니다.


3. 컴포넌트 분리와 Styled Components 활용

컴포넌트를 언제 분리해야 할까?

컴포넌트를 언제 분리해야 할지 고민될 때는, 중복되는 TSX 코드를 기준으로 삼으면 판단하기 편합니다. 컴포넌트로 분리한 후에는 다양한 곳에서 재사용할 수 있도록 외부에서 제어 가능하게 설계하는 것이 중요합니다.

커스텀 컴포넌트에 스타일 확장하기

styled-components 를 사용할 때, 기본 HTML 태그뿐만 아니라 이미 만들어진 커스텀 컴포넌트를 감싸서 스타일을 덧입히고 확장할 수 있습니다.

import styled from 'styled-components';
import Button from './common/Button';

// 기존 Button 컴포넌트를 상속받아 스타일을 확장합니다
const LikeButtonStyle = styled(Button)`
  background-color: red;
  color: white;
  border-radius: 8px;
`;

function LikeButton() {
  return (
    <LikeButtonStyle size="medium" scheme="primary" disabled={false} isLoading={false}>
      ♥ 좋아요
    </LikeButtonStyle>
  );
}

styled(Button) 처럼 커스텀 컴포넌트를 감싸면 기존 버튼의 기능은 그대로 유지하면서 스타일만 덮어씌울 수 있습니다. 공통 컴포넌트를 기반으로 페이지마다 다른 디자인의 버튼을 빠르게 만들 수 있어 재사용성이 높아집니다.


4. 장바구니 추가 기능과 임시 속성 (Transient Props)

장바구니 낙관적 업데이트

장바구니 추가 기능도 낙관적 업데이트 로직을 적용해 사용자의 클릭에 화면이 먼저 반응하도록 만듭니다.

const [added, setAdded] = useState(false);

const handleAddToCart = async () => {
  setAdded(true);

  try {
    await addToCartAPI(bookId);
  } catch (error) {
    setAdded(false);
  }
};

Transient Props ($ 접두사)

styled-components 에서 조건부 스타일링을 위해 boolean 타입의 props 를 넘겨주는 경우가 있습니다. 이때 해당 속성이 실제 DOM 요소에 그대로 전달되면 표준 HTML 속성이 아니라는 이유로 경고나 에러가 발생할 수 있습니다.

변수명 앞에 $ 기호를 붙여 임시 속성(Transient Props) 으로 만들면 리액트가 DOM으로 속성을 전달하지 않아 에러를 깔끔하게 해결할 수 있습니다.

// ❌ DOM에 added 속성이 그대로 전달되어 에러 발생
interface CartButtonProps {
  added: boolean;
}

const CartButton = styled.button<CartButtonProps>`
  background-color: ${({ added }) => (added ? 'gray' : 'blue')};
`;

// ✅ $ 접두사로 DOM 전달을 막습니다
interface CartButtonProps {
  $added: boolean;
}

const CartButton = styled.button<CartButtonProps>`
  background-color: ${({ $added }) => ($added ? 'gray' : 'blue')};
  color: white;
  padding: 0.5rem 1rem;
  border: none;
  border-radius: 4px;
  cursor: pointer;
`;

$added 처럼 $ 를 붙이면 styled-components 가 스타일 계산에만 사용하고 실제 DOM에는 전달하지 않습니다. 조건부 스타일링 props를 다룰 때 꼭 기억해야 할 패턴입니다.

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

0개의 댓글