03. Toss의 use-funnel (1)

Gardener·2025년 5월 31일

WatSeo

목록 보기
3/4

0.들어가며

이탈률에 대한 고민은 여전하다. 어떻게 하면 귀찮은 회원가입이나, 설문조사와 같은 단계에 있어서 서비스 이용자가 포기하지 않고 서비스를 지속해서 사용할 수 있을지에 대한 고민은 여전하다. 행운이지만, Toss는 이런 고민을 하는 서비스 회사라는 것이 다행이다.

혼자서 진행하던 사이드 프로젝트에서, Toss의 use-funnel 을 사용해보고 싶었고, 운이 나쁜건지 (결국 해냈지만) React Native와의 연동 과정에서 험한 꼴을 당했지만 결국 해결해낸 과정을 서술한다. 2번의 과정에 걸쳐서 작성해야할 것 같다.

1편에서는 use-funnel의 정의와 사용 목적에 대해서 알아보고, 2편에서는 내가 React Native에서 어떻게 사용했는지 알아본다.

1. use-funnel

Toss에서 만든 React 기반의 상태 관리 라이브러리
회원가입, 주문 등 단계 기반 플로우의 타입을 안전하게 관리한다.

장점

  • 단계별 context 상태를 안전하게 이어받음.
  • 뒤로가기/앞으로 가기에 따른 상태 복원 가능
  • TypeScript와 호환됨 (step 별 context 타입 정의)
  • React Native, Next.js, React Router등과의 연동 지원
npm install @use-funnel/core

@use-funnel/browser - 브라우저 기반
@use-funnel/react-router - React Router 사용 시
@use-funnel/next - Next 사용 시
@use-funnel/react-navigation-native - React Native

2. 타입 구조 설계

type SignupFunnel = {
  Email: { email: string };
  Password: { email: string; password: string };
  Profile: { email: string; password: string; name: string };
  Confirm: { email: string; password: string; name: string; agreed: boolean };
};

딱 봐도 느낌이 오듯이, 각 단계 이름과 해당 단계에서 사용할 상태 (context)를 명시한다. step 전환시마다 context를 누적하여 넘기고, 이에 타입 안전성을 보장받을 수 있다.

3. @use-funnel/browser

import {
  useFunnel,
  FunnelProvider,
  FunnelStep,
  useFunnelContext,
} from '@use-funnel/browser';

type SignupFunnel = {
  Email: {};
  Password: { email: string };
};

export default function SignupFlow() {
  const [funnel, setFunnel] = useFunnel<SignupFunnel>({
    id: 'signup-funnel',
    initial: { step: 'Email', context: {} },
  });

  return (
    <FunnelProvider value={{ funnel, setFunnel }}>
      <FunnelStep name="Email">
        <EmailComponent />
      </FunnelStep>
      <FunnelStep name="Password">
        <PasswordComponent />
      </FunnelStep>
    </FunnelProvider>
  );
}
  • useFunnel<SignupFunnel>() : stepcontext 를 관리하며 상태 전환 함수(setFunnel)을 반환
  • SignupFunnel : 각 단계 (step)의 이름과 해당 단계에서 필요한 context 타입을 정의한 TypeScript 객체
  • FunnelProvider : 상태 (funnel, setFunnel)를 하위 컴포넌트에 전달하는 React Context Provider
  • FunnelStep name="name.." : 현재 step 값이 name과 일치할 때에만 자식 컴포넌트를 렌더링함
  • useFunnelContext : 하위 컴포넌트 (email, password) 에서 현재 funnel 상태와 setFunnel 을 가져올 수 있게 해줌
  • id : 내부적으로 히스토리 상태 저장 시 유일한 키값 으로 사용됨. ex) signup-funnel, pay-funnel 등 여러 퍼널을 동시에 운영하는 경우 id가 중복되면 안되기에 반드시 고유하게 설정해야함.
import React, { useState } from 'react';
import { useFunnelContext } from '@use-funnel/browser';

function EmailComponent() {
  const { setFunnel } = useFunnelContext<SignupFunnel>();
  const [email, setEmail] = useState('');

  const handleNext = () => {
    // 간단한 유효성 검사
    const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
    if (!isValid) {
      alert('올바른 이메일 형식을 입력해주세요.');
      return;
    }

    // context에 email 저장 후 Password 단계로 이동
    setFunnel({
      step: 'Password',
      context: { email },
    });
  };

  return (
    <div>
      <h2>이메일 입력</h2>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="example@domain.com"
      />
      <button onClick={handleNext} disabled={!email}>
        다음
      </button>
    </div>
  );
}

export default EmailComponent;
    // context에 email 저장 후 Password 단계로 이동
    setFunnel({
      step: 'Password',
      context: { email },
    });
  };

이 부분을 자세히 확인하면 되는데 다음 funnel 단계 (password)를 위해 context에 email을 setFunnel을 통해 저장한 모습이다.

이렇게 context에 저장된다면, 뒤로가기/앞으로가기 동작 시 유지된 context의 내용을 확인할 수 있다.

  • 내부에서 history.pushState를 사용하거나, react-router를 연동하여 사용할 수 있다. (URL query 혹은 pathname으로 동기화)
profile
영혼의 정원수

0개의 댓글