본문으로 바로가기
윤창원uiwwsw · 작은 우주

개발과 기술

하나의 UI를 나누어 보여주는 Step 컴포넌트

2026-09-10: 설명과 코드 예시를 보완했습니다. 아래 예시는 글의 설계 의도를 전달하기 위한 것이며, 원 프로젝트에 반영된 변경 내역과는 구분합니다.

회원가입이나 날짜 입력은 같은 항목을 PC에서는 한 화면에, 모바일에서는 여러 단계로 보여줄 수 있다. 입력 항목 자체를 중복 작성하지 않고 표시 방식만 바꾸고 싶어 Step 컴포넌트를 만들었다.

표현 모드를 분리하기

step은 현재 단계의 0부터 시작하는 번호다. showAll이 true이면 모든 단계를 함께 보여준다. 이전 이름인 release는 배포 환경과 혼동될 수 있어 표현 목적이 드러나는 이름으로 바꿨다.

import { Children, type PropsWithChildren } from 'react';
import { AnimatePresence, motion, useReducedMotion } from 'framer-motion';

type StepProps = PropsWithChildren<{ step: number; showAll?: boolean }>;

export function Step({ children, step, showAll = false }: StepProps) {
  const reduceMotion = useReducedMotion();
  const steps = Children.toArray(children);

  if (showAll) return <>{children}</>;
  if (!Number.isInteger(step) || step < 0 || step >= steps.length) return null;

  return (
    <AnimatePresence mode="wait" initial={false}>
      <motion.div
        key={step}
        initial={{ opacity: 0, x: reduceMotion ? 0 : 20 }}
        animate={{ opacity: 1, x: 0 }}
        exit={{ opacity: 0, x: reduceMotion ? 0 : -20 }}
        transition={{ duration: reduceMotion ? 0 : 0.3 }}
      >
        {steps[step]}
      </motion.div>
    </AnimatePresence>
  );
}

Children.toArray는 children을 순회하기 쉽게 만들지만, 컴포넌트가 나중에 렌더링하는 내부나 Fragment 안까지 단계로 펼쳐주지는 않는다. 각 단계를 직접 자식으로 전달하는 계약이 필요하다. React Children

표시를 나누는 것과 상태를 보존하는 것

이 컴포넌트는 보여주는 단계를 고른다. 이전 단계가 언마운트되면 그 안의 로컬 상태는 사라질 수 있으므로 폼 데이터는 상위 상태나 폼 모델에서 보관해야 한다.

showAll은 애니메이션만 끄는 옵션이 아니다. 모든 항목을 보여주는 별도의 표현 모드다. 화면 크기에 따라 이 값을 바꾼다면 전환 시의 상태 보존과 SSR 초기 화면도 확인해야 한다.

단계 이동 후 포커스를 어디로 보낼지, 오류가 있는 이전 단계로 어떻게 돌아갈지도 사용자 흐름에서 정해야 한다. 작은 Step 컴포넌트가 유효성 검사와 전체 폼 상태까지 맡을 필요는 없다.

이 구조로 얻고 싶은 것은 하나의 UI 정의를 상황에 맞게 보여주는 유연함이다. 표시 방식과 입력 데이터의 책임을 나누면 적용 조건도 더 분명해진다.

Assisted by AI

윤창원이 벨로그에 남긴 글을 이 작은 우주에도 모았습니다. 사진은 누르면 원본 크기로 볼 수 있습니다. 원문의 전체 서식 보기 ↗

모든 글 둘러보기 →