https://www.youtube.com/watch?v=NwLWX2RNVcw
프로젝트에서 온보딩 화면을 구현하게 되었는데, 같은 팀원이 퍼널 구조를 도입해보자며 보내준 동영상이다.
퍼널(Funnel) 이란

사용자가 목표 지점까지 도달하는 일련의 과정이라 할 수 있다.
회원가입 시 사용자 정보를 묻는 과정, 설문조사의 질문 페이지 등을 예시로 들 수 있다.

MBTI 검사 역시 일종의 퍼널이라 할 수 있다. 일련의 단계들을 거쳐 최종 결과를 도출해내는 구조이다.
이 과정 속에서 여러가지 컴포넌트들과 데이터가 필요하고, 이 중 반복되는 요소들도 존재할 것이다.
토스에서는 이러한 퍼널을 관리하기 위하여 useFunnel 이라는 훅을 제작하였다고 한다.
useFunnel 구현해보기

const steps = [
{ name: 'Step1', component: OnBoardingStep1, nextStep: 'Step2' },
{ name: 'Step2', component: OnBoardingStep2, nextStep: 'Step3' },
{ name: 'Step3', component: OnBoardingStep3, nextStep: 'Step4' },
{ name: 'Step4', component: OnBoardingStep4, nextStep: 'Step6' },
{ name: 'Step6', component: OnBoardingStep6, nextStep: '/main' },
];
퍼널에 사용될 컴포넌트들의 정보를 담은 steps 배열이다.
현재 스텝의 이름과 다음 스텝의 이름의 비교를 통하여 다음 화면을 렌더링할 것이다.
//useFunnel.jsx
import { useState } from 'react';
export function useFunnel( defaultStep ){
const [currentStep, setCurrentStep] = useState(defaultStep);
const Step = ({ children }) => {
return <>{children}</>
};
const Funnel = ({ children }) => {
const targetStep = children.find(childStep => childStep.props.name === currentStep );
return <>{targetStep}</>
};
return [Funnel, Step, currentStep, setCurrentStep];
}
currentStep: step의 이름값을 가지는 state. defaultStep 은 첫 step의 name 인 Step1 을 할당해주었다.
setCurrentStep: step 의 상태 변화 함수
Step: step을 렌더링하는 컴포넌트
Funnel: 해당하는 단계의 step 을 렌더링하는 컴포넌트
const [Funnel, Step, currentStep, setCurrentStep] = useFunnel('Step1');
적용하려는 페이지에서 다음과 같이 useFunnel 훅을 호출해주었다.
<Funnel>
{steps.map((step, idx) => (
<Step key={idx} name={step.name}>
<step.component onNext={handleNext}/>
</Step>
))}
</Funnel>
Step 컴포넌트는 steps 배열의 컴포넌트를 렌더링하고 있다.
Funnel 컴포넌트는 Step 컴포넌트에서 렌더링하고 있는 step의 name 이 현재 보여줄 step의 name과 일치하면 이를 렌더링해준다.
const handleNext = () => {
const nextStepIndex = steps.findIndex((step) => step.name === currentStep) + 1;
if (nextStepIndex < steps.length) {
setCurrentStep(steps[nextStepIndex].name);
} else {
navigate(steps[nextStepIndex - 1].nextStep);
}
};
currentStep을 변경해주는 handleNext 함수이다.
다음 step의 name을 찾아서 상태를 갱신해주었다. 갱신된 상태에 따라 Funnel 컴포넌트에서 타겟으로 하는 Step 컴포넌트가 변경되기 때문에 새로운 step 화면이 렌더링될 수 있다.
마지막 step의 경우는 다른 페이지로 이동할 수 있도록 useNavigate 훅을 이용해주었다.
전체 코드이다.
function OnBoarding() {
const navigate = useNavigate();
const steps = [
{ name: 'Step1', component: OnBoardingStep1, nextStep: 'Step2' },
{ name: 'Step2', component: OnBoardingStep2, nextStep: 'Step3' },
{ name: 'Step3', component: OnBoardingStep3, nextStep: 'Step4' },
{ name: 'Step4', component: OnBoardingStep4, nextStep: 'Step6' },
{ name: 'Step6', component: OnBoardingStep6, nextStep: '/main' },
];
const [Funnel, Step, currentStep, setCurrentStep] = useFunnel('Step1');
const handleNext = () => {
const nextStepIndex = steps.findIndex((step) => step.name === currentStep) + 1;
if (nextStepIndex < steps.length) {
setCurrentStep(steps[nextStepIndex].name);
} else {
navigate(steps[nextStepIndex - 1].nextStep);
}
};
return (
<S.OnBoardingPageWrapper>
<Funnel>
{steps.map((step, idx) => (
<Step key={idx} name={step.name}>
<S.ProgressWrapper>
<StepProgress steps={steps} cur={step.name} />
</S.ProgressWrapper>
<step.component onNext={handleNext}/>
</Step>
))}
</Funnel>
</S.OnBoardingPageWrapper>
);
}
export default OnBoarding;
다음과 같이 Funnel 구조를 사용할 경우
1. 상위 컴포넌트에서 흐름을 관리하고, 하위 컴포넌트에서 세부 UI를 관리할 수 있다.
상태 변화의 흐름을 한눈에 보기 쉬우며 각각의 컴포넌트 관리도 더 편하게 할 수 있다.
2. 상태 변화를 위한 함수를 상위에서 한번에 관리할 수 있다.
3. 공통된 로직을 통하여 재사용할 수 있다.
실제로 온보딩 화면에서 사용한 useFunnel 훅을 여러 질문에 대한 답을 하는 페이지에서 재사용하였다.
<Funnel>
{steps.map((step, idx) => (
<Step key={idx} name={step.name}>
<step.component
onNext={handleNext}
onPrev={handlePrev}
/>
<S.ProgressWrapper>
<StepProgress steps={steps} cur={step.name} />
</S.ProgressWrapper>
</Step>
))}
</Funnel>
한 번 생성한 useFunnel 훅을 다른 페이지에서도 이용할 수 있다.
해당 페이지에서는 뒤로가기 버튼도 추가하여 용도에 맞게 기능을 추가하여 구현할 수 있었다.
진행 상황을 Progress 바로 표현하기

Progress '바' 라는 표현이 적절한 건지는 모르겠지만,,, 위와 여러 단계중 해당 단계를 표현하는 이미지를 렌더링 하는
컴포넌트를 함께 구현해보았다.
퍼널 구조를 위해 선언한 steps 배열을 활용하였다.
<Step key={idx} name={step.name}>
<step.component
onNext={handleNext}
onPrev={handlePrev}
/>
<S.ProgressWrapper>
<StepProgress steps={steps} cur={step.name} />
</S.ProgressWrapper>
</Step>
하위 컴포넌트에서 StepProgress를 관리하게 된다면 각 컴포넌트마다 StepProgress 컴포넌트를 호출해야 하며, 현재 단계를 props로 2번 넘겨주어야 하는 불편함이 존재한다.
StepProgress 역시 상태의 흐름과 관련되었다고 생각했기 때문에 상위 컴포넌트에서 같이 렌더링해주었다.
function StepProgress({ steps, cur }){
return(
<S.StepProgressWrapper>
{steps.map((step, idx)=>(
cur === step.name
?
<IcCurrentProgressCircle key = {idx} />
:
<IcProgressCircle key = {idx} />
))}
</S.StepProgressWrapper>
);
}
export default StepProgress;
StepProgress 컴포넌트에서는 현재 step이 타겟 step과 동일하다면 단계를 표현하는 이미지를 렌더링하도록 하였다.
useFunnel 훅을 처음 본다면 코드를 이해하기 어려울 수 있다고 생각한다.. 나도 그랬기 때문에...
Step 컴포넌트를 통하여 하위 컴포넌트를 렌더링하고
Funnel 컴포넌트를 통하여 해당 컴포넌트가 타겟 컴포넌트인지를 확인 후 이를 렌더링 하는 구조임을 생각하면 이해하기 쉬울 것이다.
또 이러한 상태 관리가 useFunnel 내부에서 이루어지며 이를 통하여 상위 컴포넌트에서 컴포넌트의 흐름을 관리한다는 점도 생각하면 좋을 것 같다.
https://www.slash.page/ko/libraries/react/use-funnel/README.i18n
useFunnel | Slash libraries
선언적이고 명시적으로 퍼널 스텝을 관리하고, 퍼널에 대한 상태의 흐름을 명확하게 파악할 수 있도록 만든 퍼널 컨트롤러입니다.
www.slash.page
'웹 > React' 카테고리의 다른 글
[React/리액트] Vercel로 배포하기 (0) | 2024.09.13 |
---|---|
[React/리액트] 반복되는 요소를 배열을 통해 렌더링하기 (0) | 2024.09.10 |
[React/리액트] TypeScript 에서 props 넘겨주기 (0) | 2024.09.05 |
[React/리액트] useReducer로 상태 관리하기 (1) | 2024.08.21 |
[React/리액트] 객체(배열)를 복사하고 부분만 업데이트하기 (0) | 2024.07.26 |
https://www.youtube.com/watch?v=NwLWX2RNVcw
프로젝트에서 온보딩 화면을 구현하게 되었는데, 같은 팀원이 퍼널 구조를 도입해보자며 보내준 동영상이다.
퍼널(Funnel) 이란

사용자가 목표 지점까지 도달하는 일련의 과정이라 할 수 있다.
회원가입 시 사용자 정보를 묻는 과정, 설문조사의 질문 페이지 등을 예시로 들 수 있다.

MBTI 검사 역시 일종의 퍼널이라 할 수 있다. 일련의 단계들을 거쳐 최종 결과를 도출해내는 구조이다.
이 과정 속에서 여러가지 컴포넌트들과 데이터가 필요하고, 이 중 반복되는 요소들도 존재할 것이다.
토스에서는 이러한 퍼널을 관리하기 위하여 useFunnel 이라는 훅을 제작하였다고 한다.
useFunnel 구현해보기

const steps = [
{ name: 'Step1', component: OnBoardingStep1, nextStep: 'Step2' },
{ name: 'Step2', component: OnBoardingStep2, nextStep: 'Step3' },
{ name: 'Step3', component: OnBoardingStep3, nextStep: 'Step4' },
{ name: 'Step4', component: OnBoardingStep4, nextStep: 'Step6' },
{ name: 'Step6', component: OnBoardingStep6, nextStep: '/main' },
];
퍼널에 사용될 컴포넌트들의 정보를 담은 steps 배열이다.
현재 스텝의 이름과 다음 스텝의 이름의 비교를 통하여 다음 화면을 렌더링할 것이다.
//useFunnel.jsx
import { useState } from 'react';
export function useFunnel( defaultStep ){
const [currentStep, setCurrentStep] = useState(defaultStep);
const Step = ({ children }) => {
return <>{children}</>
};
const Funnel = ({ children }) => {
const targetStep = children.find(childStep => childStep.props.name === currentStep );
return <>{targetStep}</>
};
return [Funnel, Step, currentStep, setCurrentStep];
}
currentStep: step의 이름값을 가지는 state. defaultStep 은 첫 step의 name 인 Step1 을 할당해주었다.
setCurrentStep: step 의 상태 변화 함수
Step: step을 렌더링하는 컴포넌트
Funnel: 해당하는 단계의 step 을 렌더링하는 컴포넌트
const [Funnel, Step, currentStep, setCurrentStep] = useFunnel('Step1');
적용하려는 페이지에서 다음과 같이 useFunnel 훅을 호출해주었다.
<Funnel>
{steps.map((step, idx) => (
<Step key={idx} name={step.name}>
<step.component onNext={handleNext}/>
</Step>
))}
</Funnel>
Step 컴포넌트는 steps 배열의 컴포넌트를 렌더링하고 있다.
Funnel 컴포넌트는 Step 컴포넌트에서 렌더링하고 있는 step의 name 이 현재 보여줄 step의 name과 일치하면 이를 렌더링해준다.
const handleNext = () => {
const nextStepIndex = steps.findIndex((step) => step.name === currentStep) + 1;
if (nextStepIndex < steps.length) {
setCurrentStep(steps[nextStepIndex].name);
} else {
navigate(steps[nextStepIndex - 1].nextStep);
}
};
currentStep을 변경해주는 handleNext 함수이다.
다음 step의 name을 찾아서 상태를 갱신해주었다. 갱신된 상태에 따라 Funnel 컴포넌트에서 타겟으로 하는 Step 컴포넌트가 변경되기 때문에 새로운 step 화면이 렌더링될 수 있다.
마지막 step의 경우는 다른 페이지로 이동할 수 있도록 useNavigate 훅을 이용해주었다.
전체 코드이다.
function OnBoarding() {
const navigate = useNavigate();
const steps = [
{ name: 'Step1', component: OnBoardingStep1, nextStep: 'Step2' },
{ name: 'Step2', component: OnBoardingStep2, nextStep: 'Step3' },
{ name: 'Step3', component: OnBoardingStep3, nextStep: 'Step4' },
{ name: 'Step4', component: OnBoardingStep4, nextStep: 'Step6' },
{ name: 'Step6', component: OnBoardingStep6, nextStep: '/main' },
];
const [Funnel, Step, currentStep, setCurrentStep] = useFunnel('Step1');
const handleNext = () => {
const nextStepIndex = steps.findIndex((step) => step.name === currentStep) + 1;
if (nextStepIndex < steps.length) {
setCurrentStep(steps[nextStepIndex].name);
} else {
navigate(steps[nextStepIndex - 1].nextStep);
}
};
return (
<S.OnBoardingPageWrapper>
<Funnel>
{steps.map((step, idx) => (
<Step key={idx} name={step.name}>
<S.ProgressWrapper>
<StepProgress steps={steps} cur={step.name} />
</S.ProgressWrapper>
<step.component onNext={handleNext}/>
</Step>
))}
</Funnel>
</S.OnBoardingPageWrapper>
);
}
export default OnBoarding;
다음과 같이 Funnel 구조를 사용할 경우
1. 상위 컴포넌트에서 흐름을 관리하고, 하위 컴포넌트에서 세부 UI를 관리할 수 있다.
상태 변화의 흐름을 한눈에 보기 쉬우며 각각의 컴포넌트 관리도 더 편하게 할 수 있다.
2. 상태 변화를 위한 함수를 상위에서 한번에 관리할 수 있다.
3. 공통된 로직을 통하여 재사용할 수 있다.
실제로 온보딩 화면에서 사용한 useFunnel 훅을 여러 질문에 대한 답을 하는 페이지에서 재사용하였다.
<Funnel>
{steps.map((step, idx) => (
<Step key={idx} name={step.name}>
<step.component
onNext={handleNext}
onPrev={handlePrev}
/>
<S.ProgressWrapper>
<StepProgress steps={steps} cur={step.name} />
</S.ProgressWrapper>
</Step>
))}
</Funnel>
한 번 생성한 useFunnel 훅을 다른 페이지에서도 이용할 수 있다.
해당 페이지에서는 뒤로가기 버튼도 추가하여 용도에 맞게 기능을 추가하여 구현할 수 있었다.
진행 상황을 Progress 바로 표현하기

Progress '바' 라는 표현이 적절한 건지는 모르겠지만,,, 위와 여러 단계중 해당 단계를 표현하는 이미지를 렌더링 하는
컴포넌트를 함께 구현해보았다.
퍼널 구조를 위해 선언한 steps 배열을 활용하였다.
<Step key={idx} name={step.name}>
<step.component
onNext={handleNext}
onPrev={handlePrev}
/>
<S.ProgressWrapper>
<StepProgress steps={steps} cur={step.name} />
</S.ProgressWrapper>
</Step>
하위 컴포넌트에서 StepProgress를 관리하게 된다면 각 컴포넌트마다 StepProgress 컴포넌트를 호출해야 하며, 현재 단계를 props로 2번 넘겨주어야 하는 불편함이 존재한다.
StepProgress 역시 상태의 흐름과 관련되었다고 생각했기 때문에 상위 컴포넌트에서 같이 렌더링해주었다.
function StepProgress({ steps, cur }){
return(
<S.StepProgressWrapper>
{steps.map((step, idx)=>(
cur === step.name
?
<IcCurrentProgressCircle key = {idx} />
:
<IcProgressCircle key = {idx} />
))}
</S.StepProgressWrapper>
);
}
export default StepProgress;
StepProgress 컴포넌트에서는 현재 step이 타겟 step과 동일하다면 단계를 표현하는 이미지를 렌더링하도록 하였다.
useFunnel 훅을 처음 본다면 코드를 이해하기 어려울 수 있다고 생각한다.. 나도 그랬기 때문에...
Step 컴포넌트를 통하여 하위 컴포넌트를 렌더링하고
Funnel 컴포넌트를 통하여 해당 컴포넌트가 타겟 컴포넌트인지를 확인 후 이를 렌더링 하는 구조임을 생각하면 이해하기 쉬울 것이다.
또 이러한 상태 관리가 useFunnel 내부에서 이루어지며 이를 통하여 상위 컴포넌트에서 컴포넌트의 흐름을 관리한다는 점도 생각하면 좋을 것 같다.
https://www.slash.page/ko/libraries/react/use-funnel/README.i18n
useFunnel | Slash libraries
선언적이고 명시적으로 퍼널 스텝을 관리하고, 퍼널에 대한 상태의 흐름을 명확하게 파악할 수 있도록 만든 퍼널 컨트롤러입니다.
www.slash.page
'웹 > React' 카테고리의 다른 글
[React/리액트] Vercel로 배포하기 (0) | 2024.09.13 |
---|---|
[React/리액트] 반복되는 요소를 배열을 통해 렌더링하기 (0) | 2024.09.10 |
[React/리액트] TypeScript 에서 props 넘겨주기 (0) | 2024.09.05 |
[React/리액트] useReducer로 상태 관리하기 (1) | 2024.08.21 |
[React/리액트] 객체(배열)를 복사하고 부분만 업데이트하기 (0) | 2024.07.26 |