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 컴포넌트가 유효성 검사와 전체 폼 상태까지 맡을 필요는 없다.
입력 항목은 한 번 정의하고, 화면에 따라 한꺼번에 보이거나 나누어 보이게 한다. Step에는 그 일까지만 맡겼다. 사용자가 어느 단계에 있든 입력한 내용이 이어지는지는 폼을 구성하는 쪽에서 챙긴다.
Assisted by AI
© uiwwsw · 본인 소유의 글·이미지에 대한 무단 복제·재게시를 허용하지 않습니다. 개인 열람·원문 링크 공유·검색 색인과 제한적인 미리보기는 허용합니다. 법령상 이용 예외, 기존 이용 허락, 코드·제삼자 자료의 기존 라이선스는 유지합니다. 콘텐츠 이용 안내