아직 수정 중인 게시물 입니다..⚙️
1탄에서는 Funnel, Step 컴포넌트
를 살펴봤으니 useFunnel 훅
내부 구현 방식을 살펴보자.
이번 전체 코드는 여기서 볼 수 있다.
useFunnel
, createFunnelStorage
, useFunnelState
로 나눌 수 있을 것 같다.useFunnel
은 steps, options를 props로 받고 FunnelComponent, setStep, withState를 반환한다.createFunnelStorage
는 withState()를 구현하기 위한 useFunnelState
를 정의하기 위해 작성되어 있다.먼저 useFunnel
부터 자세히 살펴보자.
export const useFunnel = <Steps extends NonEmptyArray<string>>(
steps: Steps,
options?: {
stepQueryKey?: string;
initialStep?: Steps[number];
onStepChange?: (name: Steps[number]) => void;
}
): readonly [FunnelComponent<Steps>, (step: Steps[number], options?: SetStepOptions) => void] & {
withState: <StateExcludeStep extends Record<string, unknown> & { step?: never }>(
initialState: StateExcludeStep
) => [
FunnelComponent<Steps>,
StateExcludeStep,
(
next:
| Partial<StateExcludeStep & { step: Steps[number] }>
| ((next: Partial<StateExcludeStep & { step: Steps[number] }>) => StateExcludeStep & { step: Steps[number] })
) => void
];
} => {
// 생략
}
1. props type
props로는 steps 배열
과 options
를 optional하게 받는다.
options의 속성으로는 아래와 같다.
stepQuerykey
: 문자열initialStep
: Steps에 number 인덱스로 접근한 배열 요소onStepChange
: Steps 배열 요소를 parameter로 받고 리턴 값이 없는 함수2. return type
리턴 타입 너무 살벌해 보이지만.. 천천히 살펴보자
readonly [FunnelComponent<Steps>, (step: Steps[number], options?: SetStepOptions) => void]
FunnelComponent<Steps>
타입은 아래에서 확인할 수 있다.// FunnelProps에서 'steps', 'step' 속성 제거한 타입:
type RouteFunnelProps<Steps extends NonEmptyArray<string>> = Omit<FunnelProps<Steps>, 'steps' | 'step'>;
// 함수랑 컴포넌트 합친(&) 타입
type FunnelComponent<Steps extends NonEmptyArray<string>> = ((props: RouteFunnelProps<Steps>) => JSX.Element) & {
Step: (props: StepProps<Steps>) => JSX.Element;
};
잠깐! Omit에 대해 아시는지~!? Omit: 특정 속성만 제거한 타입을 정의
(step: Steps[number], options?: SetStepOptions) => void]
& {
withState: <StateExcludeStep extends Record<string, unknown> & { step?: never }>(
initialState: StateExcludeStep
) => [
FunnelComponent<Steps>,
StateExcludeStep,
(
next:
| Partial<StateExcludeStep & { step: Steps[number] }>
| ((next: Partial<StateExcludeStep & { step: Steps[number] }>) => StateExcludeStep & { step: Steps[number] })
) => void
];
&
연산자로 확장된 타입이다. const router = useRouter();
const stepQueryKey = options?.stepQueryKey ?? DEFAULT_STEP_QUERY_KEY;
assert(steps.length > 0, 'steps가 비어있습니다.');
const FunnelComponent = useMemo(
() =>
Object.assign(
function RouteFunnel(props: RouteFunnelProps<Steps>) {
// eslint-disable-next-line react-hooks/rules-of-hooks
const step = useQueryParam<Steps[number]>(stepQueryKey) ?? options?.initialStep;
assert(
step != null,
`표시할 스텝을 ${stepQueryKey} 쿼리 파라미터에 지정해주세요. 쿼리 파라미터가 없을 때 초기 스텝을 렌더하려면 useFunnel의 두 번째 파라미터 options에 initialStep을 지정해주세요.`
);
return <Funnel<Steps> steps={steps} step={step} {...props} />;
},
{
Step,
}
),
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
const setStep = useCallback(
(step: Steps[number], setStepOptions?: SetStepOptions) => {
const { preserveQuery = true, query = {} } = setStepOptions ?? {};
const url = `${QS.create({
...(preserveQuery ? router.query : undefined),
...query,
[stepQueryKey]: step,
})}`;
options?.onStepChange?.(step);
switch (setStepOptions?.stepChangeType) {
case 'replace':
router.replace(url, undefined, {
shallow: true,
});
return;
case 'push':
default:
router.push(url, undefined, {
shallow: true,
});
return;
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[options, router]
);
- Omit: 여러개 타입 제외할때 (multiple key)
|
사용