💡 적용 결과 미리 살펴보기
잇핏 프로젝트에서는 사용자 정보를 바탕으로 하루 적정 칼로리를 계산하는 과정에서 퍼널(Funnel) 패턴을 도입했다. 퍼널 패턴을 사용한 이유는 여러 단계를 거쳐 필요한 데이터를 점진적으로 입력받게 함으로써 사용자가 목표에 도달하도록 돕기 위함이었다.
또한, 각 단계별 진행 상황을 UI에 표시해 사용자 경험(UX)을 개선했는데, 이 덕분에 사용자는 현재 진행 상태를 쉽게 파악하며 원활하게 목표를 설정할 수 있었고, 긍정적인 피드백도 받을 수 있었다.
그러나 사용자 경험이 개선된 것과 달리, 개발자 경험(DX) 측면에서는 불편한 점이 있었다.
개발을 진행하면서 여러 페이지를 오가며 작업하는 경우가 많았다. 예를 들어, 3단계 작업을 하다가 1단계로 돌아가야 하는 경우, URL을 직접 수정하거나 새로고침을 해야만 처음부터 다시 시작할 수 있었다. 이러한 과정이 반복될수록 생산성이 떨어진다는 점이 점점 체감되었고, 보다 효율적인 방법이 필요하다고 느꼈다.
해결 방안을 모색하던 중, 토스의 퍼널 구조 관리 사례를 접하게 되었다. 토스는 퍼널 구조 관리를 위해 useFunnel 이라는 자체 라이브러리를 도입해 개발자 경험을 크게 개선하고 있었다.
토스ㅣSLASH 23 - 퍼널: 쏟아지는 페이지 한 방에 관리하기
https://www.youtube.com/watch?v=NwLWX2RNVcw&t=728s
토스 사례를 참고해 잇핏 프로젝트에도 적용할 수 있는 아이디어를 얻었고, 이를 바탕으로 퍼널 구조에서 개발자 경험을 개선하기 위한 도구를 직접 만들어보기로 했다.
퍼널 구조를 더 쉽게 이해하고 관리하기 위해 Mermiad 라이브러리
를 활용했다. Mermaid는 텍스트 기반으로 다이어그램과 차트를 작성할 수 있도록 도와주는 도구로, 복잡한 흐름을 직관적으로 파악하는 데 큰 도움이 된다.
예를들어 "첫번째에서 onClick 하면 두번째로 간다. 첫번째에서 onBlur 하면 세번째로 간다" 이런식으로 텍스트 기반으로 단계의 흐름을 표현할 수 있다.
graph TD
A[첫번째] --> |onClick| B[두번째]
A[첫번째] --> |onBlur| C[세번째]
B --> |onClick| C[세번째]
Mermaid 를 잇핏 퍼널 구조에 적용하면 다음과 같은 모양이 된다. 단계가 많고 깊어질수록 시각화
는 흐름을 파악하는데 큰 도움을 준다. 만약 중간에 단계가 추가되어도 전체 구조에서 어떤 위치에 삽입해야 할지 쉽게 파악할 수 있고 개발자/비개발자와의 커뮤니케이션에도 매우 유용하다.
✨ 따라서 Mermaid 를 프로젝트에 적용하기로 결심하면서 다음과 같은 목표를 세웠다.
그래프도 동적으로 수정된다.
토스에서는 useFunnel 라이브러리를 제공하고 있지만, 직접 구현해 보기로 했다.
useFunnel 훅은 4가지 주요 반환값으로 구성되어 있다.
Funnel
: 현재 단계를 렌더링하는 컴포넌트setStep
: 특정 단계로 이동하는 함수FunnelGraph
: 퍼널 구조를 시각적으로 표시하는 컴포넌트 (옵션)FunnelEditor
: 퍼널에서 입력받는 데이터를 수정할 수 있는 에디터 컴포넌트 (옵션)// useFunnel 사용 예시
const steps: StepData<FunnelStep>[] = [
{
name: 'goalIntro',
component: GoalIntro,
props: { onNext: () => setStep('basicInfo') },
},
{
name: 'basicInfo',
component: GoalBasicInfoStep,
props: {
onNext: (data: BasicInfoType) => {
setRegisterData((prev: GoalRegisterType) => ({ ...prev, ...data }));
setStep('weightInfo');
},
},
},
// 추가 단계들...
];
const [Funnel, setStep, FunnelGraph, FunnelEditor] = useFunnel(steps, {
initialStep: 'goalIntro',
stepQueryKey: 'goal-step',
onStepChange: (step) => {
setCurrnetStep(funnelStep.indexOf(step));
},
});
useFunnel 훅에서 반환하는 FunnelGraph
는 Mermaid 라이브러리를 활용해 퍼널 구조를 자동으로 시각화하는 컴포넌트이다. Mermaid 문법에 맞게 퍼널 단계를 변환한 뒤, 다이어그램을 동적으로 렌더링하는 방식이다.
퍼널 구조를 동적으로 mermaid 문법으로 변환
graph TD
goalIntro[goalIntro] --> |onNext| basicInfo[basicInfo]
basicInfo[basicInfo] --> |onNext| weightInfo[weightInfo]
weightInfo[weightInfo] --> |onNext| caloriesInfo[caloriesInfo]
이렇게 해서 얻은 문자열을 화면에 렌더링하려면, 먼저 useEffect 를 사용하여 페이지가 로드될때 mermaid 초기화 한다. 그리고 mermaind 를 화면에 렌더링 하도록 renderGraph 함수를 호출한다.
useEffect(() => {
mermaid.initialize({
startOnLoad: false, // 로드 시 자동 렌더링 방지
securityLevel: 'loose', // 보안 설정을 느슨하게 설정
theme: 'default', // 기본 테마 사용
});
renderGraph();
}, [steps]);
renderGraph 함수는 Mermaid 그래프를 렌더링하는 역할을 한다. 앞에서 얻은 그래프 정의 문자열을 mermaid.render(id, text, svgContainingElement?)
함수에 넣어서 호출하면 svg, bindFunctions 객체를 반환한다.
svg
Mermaid 그래프를 SVG 형식으로 반환한 결괏값으로 즉, svg 는 Mermaid로 생성한 그래프 입니다. 미리 생성해 놓은 mermaidRef DOM 요소에 할당해줌으로써 실제 웹 페이지에서 그래프를 렌더링 하도록 합니다.
bindFunctions
는 Mermaid 그래프에서 사용자와 상호작용할 수 있는 기능들을 바인딩하는 함수입니다. 예를들어 사용자가 그래프의 노드를 클릭할때 발생하는 이벤트를 처리하거나 다른 동작(이벤트) 을 정의할 수 있습니다. 저는 'click' 이벤트를 등록해서 특정 노드를 클릭하면 해당 페이지로 이동되도록 구현했습니다.
const renderGraph = async () => {
if (mermaidRef.current) {
const graphDefinition = getGraph(steps, currentStep);
mermaidRef.current.innerHTML = graphDefinition;
try {
const { svg, bindFunctions } = await mermaid.render(`mermaid-funnel-${stepQueryKey}`, graphDefinition);
mermaidRef.current.innerHTML = svg;
bindFunctions?.(mermaidRef.current);
} catch (error) {
console.error('Mermaid graph rendering failed:', error);
}
} else {
console.warn('Mermaid container is not ready.');
}
if (mermaidRef.current) {
mermaidRef.current.querySelectorAll('g.node').forEach((node) => {
node.addEventListener('click', (e) => {
const target = e.target as HTMLElement;
if (target) {
const stepName = target.innerText;
setStep(stepName as T); // 상태 업데이트
}
});
});
}
};
결국 steps 배열을 가지고 Mermaid 문법에 맞게 텍스트화한 뒤 화면에 그려주면 되는 것이다.
이때 개발 모드에서만 FunnelGraph 함수가 렌더링 되도록 설정해주면 개발할때 하단에 그래프를 on/off 할 수 있는 버튼이 생성된다.
Mermaid 를 도입하면서 더이상 URL을 변경하지 않고도 필요한 단계로 바로 이동할 수 있게 되어 개발 속도가 눈에 띄게 향상되었다. 개인 프로젝트로 혼자 개발을 진행했지만, 협업 환경에서도 이러한 시각화 도구가 있다면 커뮤니케이션을 하는 시간이나 개발하는데 있어서 많은 시간을 줄여 줄거라고 생각한다.
그러나 한가지 아쉬운 점이 있었는데, 필요한 단계로 빠르게 이동할 수 있는 편리함은 얻었지만 도구 내에서 데이터를 바로 수정하고 반영된 결과를 볼 수 있다면 더 효율적이지 않을까 하는 생각이 들었다.
기존에 퍼널 패턴에서 상태를 Context로 관리하고 있었기 때문에, 이를 활용해 현재 입력된 데이터를 한눈에 확인하고 수정할 수 있는 기능을 추가하기로 했다. FunnelEditor
라는 새로운 컴포넌트를 만들고, 데이터를 확인하고 즉시 수정할 수 있도록 했다.
Editor 버튼을 추가하여, 데이터를 한눈에 확인하고 필요시 수정할 수 있도록 했다. 이를 통해 개발을 하다가 제대로 동작하는 확인하기 위해 굳이 컴포넌트를 이동하지 않아도 Editor 를 통해서 변경된 값을 실시간으로 반영할 수 있다.
즉, 그래프는 퍼널 단계의 흐름을 한눈에 보여주고, 에디터 도구는 데이터를 확인하고 수정하는 기능을 제공하여 개발 속도를 한층 더 높이는 결과를 얻을 수 있었다.
토스팀에서는 그래프 시각화 작업을 통해 퍼널 패턴의 구조적인 단점을 극복해나갔다면, 나는 여기에 에디터 기능을 추가해 굳이 그래프를 이동하지 않아도 입력받은 데이터를 가지고 그 자리에서 직접 변경해 반영될 수 있도록 develop 했다는 부분에서 차이를 뒀다.
다시한번 개발 과정에서 느꼈던 불편함을 지나치지 않고 어떻게 개선할 수 있을지 고민하여 아이디어를 접목해 나간 토스팀에 대한 경외심과 더불어 이러한 고민과 실천이 결과적으로는 더 효율적인 개발 환경으로 이어진다는 점에서 많은 걸 느끼고 배울 수 있었다.