토스 · SLASH 23 2023
퍼널: 쏟아지는 페이지 한 방에 관리하기
단계별 UI(퍼널) 관리를 위한 커스텀 훅 useFunnel 설계와 구현 (발표: 진유림)
요약
핵심 토픽
학습 포인트
1. 퍼널 패턴과 상태 응집도
회원가입·구독 등 여러 화면에 걸쳐 데이터를 수집하는 UI를 '퍼널'이라 합니다. 라우터 기반으로 각 단계를 별도 페이지로 만들면 데이터가 전역 상태나 URL 파라미터로 흩어져 흐름 파악이 어렵습니다. 퍼널 패턴의 핵심: step 상태와 각 단계 데이터를 단일 컴포넌트(또는 훅)에 모아두면 전체 흐름을 한눈에 파악하고, 마지막 단계에서 수집된 데이터를 한 번에 API로 전송할 수 있습니다.
핵심 용어
2. useFunnel 커스텀 훅 설계 원리
퍼널 로직을 `useFunnel(['단계1', '단계2', '단계3'])`으로 초기화하면 Funnel·Step 컴포넌트와 step 상태가 반환됩니다. TypeScript 제네릭으로 단계 이름을 타입 안전하게 관리해 존재하지 않는 단계명 오타를 컴파일 타임에 잡을 수 있습니다. 라이브러리로 분리하면 브라우저 히스토리 연동 같은 부가 기능을 사용 측 코드 변경 없이 추가할 수 있습니다. 토스는 이를 @toss/slash 오픈소스로 공개했습니다.
핵심 용어
3. 브라우저 히스토리와 뒤로 가기 지원
SPA에서 퍼널의 각 단계를 History API의 pushState로 기록하면 브라우저 뒤로 가기가 이전 단계로 이동하게 됩니다. 단계 이동 시 `history.pushState`로 쿼리 파라미터에 step을 기록하고, popstate 이벤트로 뒤로 가기를 감지해 step 상태를 동기화합니다. URL에 단계 정보가 있으면 새로고침해도 현재 단계를 복원할 수 있어 UX가 향상됩니다.
핵심 용어
4. 퍼널 디버거와 개발자 경험 개선
Mermaid 라이브러리로 useFunnel 코드를 정적 분석해 플로우 다이어그램을 자동 생성합니다. 현재 스텝 강조 표시, 스텝 클릭으로 점프 기능을 제공해 개발 중 URL을 직접 수정하는 번거로움을 없앴습니다. 코드를 보지 않고도 퍼널 흐름을 PM·디자이너와 공유할 수 있어 소통 비용도 줄어듭니다. 이처럼 사용자가 아닌 개발자의 경험을 개선하는 도구를 만드는 것도 프론트엔드 엔지니어링의 중요한 부분입니다.
핵심 용어
면접 질문
Q1. 다단계 폼(마법사 UI)을 구현할 때 전역 상태와 지역 상태 중 어느 것을 사용하는 게 적절한가요?
힌트
[감점 답변] 정의만 반복하거나 "다단계 폼(마법사 UI)을 구현할 때 전역 상태와 지역 상태 중 어느 것을 사용하는 게 적절한가요?"에 대해 장단점 없이 단편적으로 답하면 감점 포인트입니다. 면접관은 실무 적용 경험이 부족하다고 판단합니다. [좋은 답변] 기본적으로 지역 상태(단일 컴포넌트 내부)가 적절하다고 답하고, 그 이유를 설명하세요: 폼 완료 전까지 전역 상태에 노출되면 다른 컴포넌트가 불완전한 데이터를 참조할 위험이 있고, 폼 이탈 시 상태 초기화를 별도로 처리해야 합니다. 전역 상태가 필요한 경우(여러 페이지에 걸쳐 오래 유지해야 하는 데이터)도 함께 언급하면 균형 있는 답변이 됩니다.
Q2. SPA에서 다단계 폼의 뒤로 가기 버튼을 지원하려면 어떻게 구현하나요?
힌트
[감점 답변] 정의만 반복하거나 "SPA에서 다단계 폼의 뒤로 가기 버튼을 지원하려면 어떻게 구현하나요?"에 대해 장단점 없이 단편적으로 답하면 감점 포인트입니다. 면접관은 실무 적용 경험이 부족하다고 판단합니다. [좋은 답변] History API의 pushState로 단계 이동 시 URL에 step을 기록하고, window.addEventListener('popstate', ...) 로 뒤로 가기를 감지해 step 상태를 동기화한다고 설명하세요. URL에 step이 있으면 새로고침 후에도 복원 가능하다는 점, 그리고 이 로직을 useFunnel 같은 추상화 레이어에 숨기면 사용 측 코드가 단순해진다는 점도 언급하면 좋습니다.
Q3. 커스텀 훅을 재사용 가능한 라이브러리로 분리할 때의 기준은 무엇인가요?
힌트
[감점 답변] 정의만 반복하거나 "커스텀 훅을 재사용 가능한 라이브러리로 분리할 때의 기준은 무엇인가요?"에 대해 장단점 없이 단편적으로 답하면 감점 포인트입니다. 면접관은 실무 적용 경험이 부족하다고 판단합니다. [좋은 답변] 세 가지 기준을 설명하세요: (1) 여러 곳에서 반복 사용되는 로직인지, (2) 도메인 로직과 공통 로직을 명확히 분리할 수 있는지(도메인이 섞이면 분리 어려움), (3) 사용 측이 내부 구현을 몰라도 사용 가능한지. 분리 후 기능 추가 시 사용 측 코드 변경 없이 라이브러리만 수정하면 된다는 장점도 언급하면 좋습니다.
Q4. TypeScript로 useFunnel을 만들 때 단계 이름을 타입 안전하게 관리하려면 어떻게 하나요?
힌트
[감점 답변] 정의만 반복하거나 "TypeScript로 useFunnel을 만들 때 단계 이름을 타입 안전하게 관리하려면 어떻게 하나요?"에 대해 장단점 없이 단편적으로 답하면 감점 포인트입니다. 면접관은 실무 적용 경험이 부족하다고 판단합니다. [좋은 답변] 제네릭 타입으로 단계 이름을 리터럴 타입으로 제한하는 방법을 설명하세요. `useFunnel<['단계1', '단계2']>` 또는 `useFunnel(['단계1', '단계2'] as const)` 패턴으로 steps 배열의 값을 유니온 타입으로 추론해, 존재하지 않는 단계명을 컴파일 타임에 에러로 잡을 수 있다고 설명하면 됩니다.
Q5. 퍼널 이탈(중간에 다른 페이지로 이동) 시 데이터를 어떻게 처리해야 하나요?
힌트
[감점 답변] 정의만 반복하거나 "퍼널 이탈(중간에 다른 페이지로 이동) 시 데이터를 어떻게 처리해야 하나요?"에 대해 장단점 없이 단편적으로 답하면 감점 포인트입니다. 면접관은 실무 적용 경험이 부족하다고 판단합니다. [좋은 답변] 두 가지 전략을 설명하세요: (1) 이탈 시 상태 초기화 — 지역 상태 사용 시 컴포넌트 언마운트로 자동 처리, (2) 중단 지점 저장 후 복원 — localStorage에 임시 저장해 다음 방문 시 이어서 진행. 어떤 전략이 더 나은지는 비즈니스 요구사항(이탈률 개선 목적이면 복원 전략이 유리)에 따라 결정된다는 점도 언급하면 좋습니다.
선행 학습
- React 커스텀 훅 작성 방법
- React Router 기초
- 브라우저 History API
핵심 타임스탬프
학습 방법
1단계: 3단계짜리 회원가입 폼을 React Router로 각 단계를 별도 페이지로 구현해보세요. 상태가 어떻게 흩어지는지 직접 체감하면 응집도 문제를 이해할 수 있습니다. 2단계: 같은 폼을 단일 컴포넌트 + step 상태 방식으로 다시 구현하고, History API로 뒤로 가기까지 지원해보세요. 3단계: @toss/slash의 useFunnel 소스코드를 읽으며 TypeScript 제네릭 활용 방법과 라이브러리 추상화 패턴을 분석해보세요. 4단계: 동료에게 "useFunnel"의 핵심을 5분 안에 설명해보세요. 막히는 부분이 아직 구조적으로 이해되지 않은 지점입니다.