아직 수정 중인 게시물 입니다..⚙️
const [퍼널, setStep] = useFunnel(['스텝1', '스텝2', '스텝3'] as const)
const [퍼널, setStep] = useFunnel(['이름입력', '주민번호입력', '완료'] as const)
return (
<퍼널>
<퍼널.Step name="이름입력">
<이름입력스텝 /> // ?funnel-step=이름입력 스텝일 때 렌더
</퍼널.Step>
<퍼널.Step name="주민번호입력">
<주민번호입력스텝 /> // ?funnel-step=주민번호입력 스텝일 때 렌더
</퍼널.Step>
<퍼널.Step name="완료">
<퍼널완료스텝 /> // ?funnel-step=완료 스텝일 때 렌더
</퍼널.Step>
</퍼널>
)
const KyoboLifeFunnel = () => {
const [Funnel, state, setState] = useFunnel(['아파트여부', '지역선택', '완료'] as const).withState<{
propertyType?: '빌라' | '아파트';
address?: string;
}>({});
const 상담신청 = useLoanApplicationCallback();
return (
<Funnel>
<Funnel.Step name="아파트여부">
<아파트여부스텝 지역선택으로가기={() => setState(prev => ({...prev, step: '지역선택', isApartment: true}))} />
</Funnel.Step>
<Funnel.Step name="지역선택">
<지역선택스텝 지역선택완료={(지역정보) => setState(prev => ({...prev, step: '완료', region: 지역정보}))} />
</Funnel.Step>
<Funnel.Step name="완료">
<완료스텝 신청={() => 상담신청(state)} />
</Funnel.Step>
</Funnel>
);
};
Funnel.tsx 전체 코드는 여기 참고!
export interface FunnelProps<Steps extends NonEmptyArray<string>> {
steps: Steps;
step: Steps[number];
children: Array<ReactElement<StepProps<Steps>>> | ReactElement<StepProps<Steps>>;
}
먼저 FunnelProps
에서 상속받는 NonEmptyArray<string>>
타입부터 살펴보자.
export type NonEmptyArray<T> = readonly [T, ...T[]];
제네릭 타입으로 만든 배열 타입을 readonly로 반환한다.
따라서 FunnelProps는 아래와 같이 정리할 수 있다.
steps
: 문자열 타입의 배열 (readonly)
step
: steps의 배열 요소(문자열), number 인덱스로 접근 가능
children
: ReactElement<StepProps<Steps>>
또는 배열
export const Funnel = <Steps extends NonEmptyArray<string>>({ steps, step, children }: FunnelProps<Steps>) => {
const validChildren = Children.toArray(children)
.filter(isValidElement)
.filter(i => steps.includes((i.props as Partial<StepProps<Steps>>).name ?? '')) as Array<
ReactElement<StepProps<Steps>>
>;
const targetStep = validChildren.find(child => child.props.name === step);
assert(targetStep != null, `${step} 스텝 컴포넌트를 찾지 못했습니다.`);
return <>{targetStep}</>;
};
위에서 선언한 FunnelProps
타입의 props를 받아서 children 중 일치하는 step을 가진 컴포넌트를 반환하는 함수이다.
잠깐! Partial 에 대해 아시는지~? Partial: 특정 타입의 부분 집합을 만족하는 타입을 정의
export interface StepProps<Steps extends NonEmptyArray<string>> {
name: Steps[number];
onEnter?: () => void;
children: ReactNode;
}
Step 함수의 props 타입을 정의해둔 부분으로, Steps
제네릭 타입을 받는다.
name
: Steps 배열에 number 인덱스로 접근한 string 배열 요소
onEnter
: () => void 타입의 함수
children
: ReactNode
export const Step = <Steps extends NonEmptyArray<string>>({ onEnter, children }: StepProps<Steps>) => {
useEffect(() => {
onEnter?.();
}, [onEnter]);
return <>{children}</>;
};
컴포넌트 마운트 될 때, onEnter가 변경될 때 useEffect 실행해서 onEnter
가 존재한다면 실행시킨다. & children 반환한다.
- 제네릭을 잘 사용한다는게 이런걸까..
- Partial과 같은 유틸리티 타입 사용
- readonly와 const, as const 차이
const
: 변수에 사용readonly
: 속성에 사용, const와 마찬가지로 object / array 안의 값들이 수정 안되는거 아님as const
: object property 값 / array의 아이템들 수정 불가능