Toss Slash useFunnel 뜯어보기 (2)

xoxristine·2024년 5월 20일
0
post-thumbnail

아직 수정 중인 게시물 입니다..⚙️

1탄에서는 Funnel, Step 컴포넌트를 살펴봤으니 useFunnel 훅 내부 구현 방식을 살펴보자.

이번 전체 코드는 여기서 볼 수 있다.

  • interface / type 정의 부분을 제외하면 크게 useFunnel, createFunnelStorage, useFunnelState로 나눌 수 있을 것 같다.
  • 이 중에서도 useFunnelsteps, options를 props로 받고 FunnelComponent, setStep, withState를 반환한다.
  • 그리고 createFunnelStorage는 withState()를 구현하기 위한 useFunnelState를 정의하기 위해 작성되어 있다.

먼저 useFunnel부터 자세히 살펴보자.

📍 useFunnel

👕 props와 return type

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]
  1. 먼저 가장 첫줄에는 readonly 튜플 타입을 정의하고 있다.
    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]
  1. 그 다음 타입은 Steps 요소 타입과 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. 그 다음 타입은 & 연산자로 확장된 타입이다.

👚 FunnelComponent

  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
    []
  );

👚 setStep

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]
  );

📍 createFunnelStorage

📍 useFunnelState

📍 배운 점 및 후기

  1. Omit: 여러개 타입 제외할때 (multiple key) | 사용
profile
🔥🦊

0개의 댓글