"ํ ์ค SLASH 23์ ํผ๋: ์์์ง๋ ํ์ด์ง ๊ด๋ฆฌํ๊ธฐ" ๋ฅผ ์ฐธ๊ณ ํ์ฌ ์์ฑ๋์์ต๋๋ค.
๋งํฌ
ํ ์ค์์๋ ๋ค๋ฅธ ํต์ ์ฌ๋ค ์ฒ๋ผ ์๊ธ์ ๊ฐ์
์ ์ฒญ์๋ฅผ ๋ฐ๊ณ ์๋ค.
์ฐจ๋ณํ๋ ์ ์ ํ ํ์ด์ง๋ก ์ด๋ฃจ์ด์ง ํผ ๋์ , ํ ํ์ด์ง์ ํ ํญ๋ชฉ๋ง ์ ์ถํ๋ UI๋ฅผ ๊ฐ์ง๊ณ ์๋ค.
ํ์ง๋ง ์ด๋ฐ์์ผ๋ก ๋ง์ ํ์ด์ง๋ค์ ํ ๋ฒ์ ๊ด๋ฆฌํ๊ธฐ๋ ์ฝ์ง ์๋ค.
์ ๋ฐํ์์๋ ์ด๋ฌํ ํ์ด์ง๋ค์ ํจ๊ณผ์ ์ผ๋ก ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ๋ํด์ ์ค๋ช
ํ๊ณ ์๋ค.
ย
ํผ๋(Funnel)
ํผ๋์ ์ฌ์ ์ ์ธ ๋ป์ธ ๊น๋๊ธฐ ์ ๊ฐ์ ๋ชจ์์ ๋ ๊ธฐ ๋๋ฌธ์ ๋ถ์ฌ์ง ์ด๋ฆ์ด๋ผ๊ณ ํ๋ค.
์ด๋ฌํ ๊น๋๊ธฐ ๋ชจ์์ธ ํผ๋์ ํ ์ค์ ํ์๊ฐ์
์ ์ฐจ์ ์ ์ฉ๋์ด ์๋ค๊ณ ํ๋ค.
๊ทธ๋ ๋ค๋ฉด ์ด๋ป๊ฒ ํ๋ก์ฐ๋ฅผ ๊ด๋ฆฌํ ์ง ๊ถ๊ธํ๋ฐ, ํ ์ค์์๋ ๋ ๊ฐ์ง ์์๋ฅผ ๋ค์ด์ฃผ์๋ค.
๊ธฐ์กด ํผ๋ ๋ฐฉ์
์๋ฃ ๋ฒํผ์ ์ด์ฉํด router๋ฅผ ์ด๋ํ๋ฉด์ ํ์ด์ง๋ง๋ค ์์งํ๋ ์ ์ ์ ์ ๋ณด๋ฅผ ์ ์ญ์ํ์ ์ ์ฅํ๋ฉฐ ๋ง์ง๋ง ํ์ด์ง์์ api๋ฅผ ํธ์ถํ๋ ์ ์์ ์ธ ๊ตฌํ๋ฐฉ๋ฒ
ํ ์ค ํผ๋ ๋ฐฉ์
useState๋ฅผ ํ์ฉํ์ฌ ์ ์ ์ ๋ณด ๋ฐ page step์ ์ง์ญ์ํ๋ก ๋ง๋ค์ด์ฃผ๊ณ , ๊ฐ์
๋ฐฉ์, ์ฃผ๋ฏผ๋ฒํธ, ์ง์ฃผ์ ๋ฑ ํ์ฌ ์ด๋ UI๋ฅผ ๋ณด์ฌ์ค์ผํ๋์ง ํ์ด์ง๋ฅผ ์กฐ๊ฑด๋ถ ์ฒ๋ฆฌํ์ฌ step์ ๋ฐ๋ผ ์ํ๋ UI๋ก ์
๋ฐ์ดํธ ์์ผ์ฃผ๋ฉฐ ๋ง์ง๋ง step์์ api๋ฅผ ํธ์ถํ๋ ๋ฐฉ๋ฒ
const [registerData, setRegisterData] = useState()
const [step, setStep] = useState<"๊ฐ์
๋ฐฉ์"|"์ฃผ๋ฏผ๋ฒํธ"|"์ง์ฃผ์"|"๊ฐ์
์ฑ๊ณต">("๊ฐ์
๋ฐฉ์")
return (
<main>
{step === "๊ฐ์
๋ฐฉ์" && <๊ฐ์
๋ฐฉ์ onNext={(data) => setStep("์ฃผ๋ฏผ๋ฒํธ")} />}
{step === "์ฃผ๋ฏผ๋ฒํธ" && <์ฃผ๋ฏผ๋ฒํธ onNext={() => setStep("์ง์ฃผ์")} />}
{step === "์ง์ฃผ์" && <์ง์ฃผ์ onNext={async () => setStep("๊ฐ์
์ฑ๊ณต")} />}
{step === "๊ฐ์
์ฑ๊ณต" && <๊ฐ์
์ฑ๊ณตStep />}
</main>
)
ย
๊ธฐ์กด ํผ๋์ ์์ฌ์ด ์ ๋ฐ ๋ณด์
๊ธฐ์กด ํผ๋ ๋ฐฉ์๋ ์๋ฒฝํด ๋ณด์ด์ง๋ง ๋ช ๊ฐ์ง ์์ฌ์ด์ ์ด ์๋ค.
ํ์ด์ง ํ๋ฆ์ด ํฉ์ด์ ธ์๋ค.
๊ฐ์ ํ๋ก์ฐ๋ฅผ ํ์ ํ๊ธฐ ์ํด์๋ 3๊ฐ์ ์ปดํฌ๋ํธ ํ์ผ์ ๋๋๋ค์ด์ผ ํ๋ค.ํ ๊ฐ์ง ๋ชฉ์ ์ ์ํ ์ํ๊ฐ ํฉ์ด์ ธ ์๋ค.
์ํ๋ฅผ ์์งํ๋ ๊ณณ๊ณผ ์ฌ์ฉํ๋ ๊ณณ์ด ๋ฌ๋ผ์, api์ ๊ธฐ๋ฅ์ ์ถ๊ฐํ๊ฑฐ๋ ๋ฒ๊ทธ๋ฅผ ์์ ํ ๋ ์ ์ฒด ํ์ด์ง๋ฅผ ๋๋๋ค๋ฉฐ ๋ฐ์ดํฐ ํ๋ฆ์ ํ์ ํด์ผ ํ๋ค.
์ด๋ฅผ ๋ณด์ํ๋ ๋ฐฉ๋ฒ์ด ํ ์ค ํผ๋ ๋ฐฉ์์์ ํ์ธํ ์ ์๋ค.
๊ทธ ๋ฐฉ๋ฒ์ผ๋ก๋ ํผ๋์ ์์ง๋๋ฅผ ๋์ด๊ณ ์ถ์ํ๋ฅผ ํตํด ๋ผ์ด๋ธ๋ฌ๋ฆฌํ๋ฅผ ์ํํ๋ค.
์ด ๋ ๊ฐ์ง ํค์๋๋ฅผ ๊ฐ์ง๊ณ ๊ธฐ์กด ๋ฐฉ์์ ๋จ์ ์ ๋ณด์ํ๋ค.
ย
์์ง๋ ๋์ด๊ธฐ
const [registerData, setRegisterData] = useState()
const [step, setStep] = useState<"๊ฐ์
๋ฐฉ์"|"์ฃผ๋ฏผ๋ฒํธ"|"์ง์ฃผ์"|"๊ฐ์
์ฑ๊ณต">("๊ฐ์
๋ฐฉ์")
return (
<main>
{step === "๊ฐ์
๋ฐฉ์" && <๊ฐ์
๋ฐฉ์ onNext={(data) => {
setRegisterData(prev => ({ ...prev, ๊ฐ์
๋ฐฉ์: data })) // ์ดํ ๋์ผ
setStep("์ฃผ๋ฏผ๋ฒํธ")
}} />}
{step === "์ฃผ๋ฏผ๋ฒํธ" && <์ฃผ๋ฏผ๋ฒํธ onNext={() => setStep("์ง์ฃผ์")} />}
{step === "์ง์ฃผ์" && <์ง์ฃผ์ onNext={async () => {
await fetch("/api/register", { data }) // API ํธ์ถ ์ฅ์ ๋ณ๊ฒฝ
setStep("๊ฐ์
์ฑ๊ณต")
}} />}
{step === "๊ฐ์
์ฑ๊ณต" && <๊ฐ์
์ฑ๊ณตStep />}
</main>
)
useState๋ฅผ ์ด์ฉํด ์ง์ญ์ํ๋ฅผ ๋ง๋ค์ด์ฃผ๊ณ ๊ฐ์ ๋ฐฉ์, ์ฃผ๋ฏผ๋ฒํธ ๋ฑ ํ์ฌ ์ด๋ UI๋ฅผ ๋ณด์ฌ์ค์ผ ํ๋์ง state์ ์ ์ฅํ๋ค.๊ทธ๋ฌ๋ฉด ํ๋์ ์ปดํฌ๋ํธ ํ์ด์ง์์ ํฉ์ด์ ธ์๋ ํ์ด์ง๋ค์ ํ ํ์ด์ง์ ์์ง์์ผ ๊ด๋ฆฌ๊ฐ ์ฉ์ดํ๊ฒ ๋ง๋ค์ด ์ค ์ ์๋ค.
๊ทธ๋ฆฌ๊ณ
step์ํ์ ๋ฐ๋ผ ๊ฐ UI ์ปดํฌ๋ํธ๋ฅผ ์กฐ๊ฑด๋ถ ๋ ๋๋งํ๊ณ , '๋ค์' ๋ฒํผ์ ๋๋ฅผ ๋step์ํ๋ฅผ ์ํ๋ UI๋ก ์ ๋ฐ์ดํธ ํ๋ค.๊ฒฐ๊ณผ์ ์ผ๋ก
step์ ์ด๋์ ์์ ์ปดํฌ๋ํธ์์ ๊ด๋ฆฌํ์ฌ UI ํ๋ฆ์ ํ ๊ณณ์์ ๊ด๋ฆฌํ ์ ์๊ฒ ๋์๋ค.
๋ง์ง๋ง์ผ๋ก API ํธ์ถ์ ํ์ํ ์ํ๋ ์์์์ ํ ๋ฒ์ ๊ด๋ฆฌํ๋ฉด ์ด๋ค ์ํ๊ฐ ์ด๋ค UI์์ ์์ง๋๊ณ ์๋์ง ํ ๋์ ๋ณผ ์ ์์ผ๋ฉฐ, ์ด์ ๋ ์ด์ ํ์ผ์ ๋๋๋ค๋ฉด์ ์ ์ญ ์ํ๋ฅผ ๊ด๋ฆฌํ์ง ์์๋ ๋๋ค.
ย
์ถ์ํ๋ก ์ฌ์ฌ์ฉ์ฑ ๋์ด๊ธฐ
const [registerData, setRegisterData] = useState()
const [step, setStep] = useState<"๊ฐ์
๋ฐฉ์"|"์ฃผ๋ฏผ๋ฒํธ"|"์ง์ฃผ์"|"๊ฐ์
์ฑ๊ณต">("๊ฐ์
๋ฐฉ์")
return (
<main>
<Step if={step === "๊ฐ์
๋ฐฉ์"}>
<๊ฐ์
๋ฐฉ์ onNext={() => setStep("์ฃผ๋ฏผ๋ฒํธ")} />
</Step>
<Step if={step === "์ฃผ๋ฏผ๋ฒํธ"}>
<์ฃผ๋ฏผ๋ฒํธ onNext={() => setStep("์ง์ฃผ์")} />
</Step>
// ...
</main>
)
๊ฐ step์ ์ปดํฌ๋ํธ๋ก ๋ฐ๋ก ๋ง๋ค์ด ์ฃผ์ด ์ค๋ณต๋ step ์ฒ๋ฆฌ๋ฅผ ์ถ์ํ ํด์ค๋ค.
๊ทธ๋ฆฌ๊ณ props๋ฅผ ์กฐ๊ธ ๋ ๊น๋ํ๊ฒ ์์ฑํด ์ค ์ ์์ ๊ฒ ๊ฐ๋ค.
const [registerData, setRegisterData] = useState()
const [step, setStep] = useState<"๊ฐ์
๋ฐฉ์"|"์ฃผ๋ฏผ๋ฒํธ"|"์ง์ฃผ์"|"๊ฐ์
์ฑ๊ณต">("๊ฐ์
๋ฐฉ์")
return (
<main>
<Step name="๊ฐ์
๋ฐฉ์">
<๊ฐ์
๋ฐฉ์ onNext={() => setStep("์ฃผ๋ฏผ๋ฒํธ")} />
</Step>
<Step name="์ฃผ๋ฏผ๋ฒํธ">
<์ฃผ๋ฏผ๋ฒํธ onNext={() => setStep("์ง์ฃผ์")} />
</Step>
// ...
</main>
)
์์ ์ฝ๋๋ฅผ ๋ณด๋ฉด ์กฐ๊ฑด๋ฌธ์ ์ญ์ ํ๊ณ name๋ง props๋ก ๋จ๊ฒผ์ผ๋ฉฐ, ์ปดํฌ๋ํธ๊ฐ ๊น๋ํด์ง ๊ฒ์ ๋ณผ ์ ์๋ค.
๋ง๋ค๊ณ ๋ณด๋ Step ์ปดํฌ๋ํธ๊ฐ ํ์ฌ ํผ๋์ step์ ์๊ณ ์์ด์ผ ํ๋ฏ๋ก, ์ด๋ฅผ ์ํด ํผ๋์์ ์ง์ ๊ด๋ฆฌํ๊ณ ์๋ step ์ํ๋ ๋ด๋ถ ๋ก์ง์ผ๋ก ์ฎ๊ฒจ์ฃผ๊ธฐ ์ํด ์ํ๋ฅผ ๋ด์ ํจ์์ธ ์ปค์คํ
ํ
์ ๋ง๋ค์ด Step ์ปดํฌ๋ํธ์ ์ํ๋ฅผ ๊ฐ์ด ๊ด๋ฆฌํ ์ ์๊ฒ ์ฝ๋๋ฅผ ์ง์ค๋๋ค. (useFunnel ์ปค์คํ
ํ
์์ฑ)
function useFunnel() {
const [step, setStep] = useState()
const Step = (props) => {
return <>{props.children}</>
}
const Funnel = ({ children }) => {
// name์ด ํ์ฌ step ์ํ์ ๋์ผํ Step๋ง ๋ ๋๋ง
const targetStep = children.find(childStep => childStep.props.name === step);
return Object.assign(targetStep, { Step })
}
return [Funnel, setStep]
}
const [registerData, setRegisterData] = useState()
const [step, setStep] = useFunnel<"๊ฐ์
๋ฐฉ์"|"์ฃผ๋ฏผ๋ฒํธ"|"์ง์ฃผ์"|"๊ฐ์
์ฑ๊ณต">("๊ฐ์
๋ฐฉ์")
return (
<Funnel>
<Funnel.Step name="๊ฐ์
๋ฐฉ์">
<๊ฐ์
๋ฐฉ์ onNext={() => setStep("์ฃผ๋ฏผ๋ฒํธ")} />
</Funnel.Step>
<Funnel.Step name="์ฃผ๋ฏผ๋ฒํธ">
<์ฃผ๋ฏผ๋ฒํธ onNext={() => setStep("์ง์ฃผ์")} />
</Funnel.Step>
// ...
</Funnel>
)
์ฌ๊ธฐ๊น์ง ํผ๋์ ์์ง๋์ ์ถ์ํ๋ฅผ ๊ฑฐ์ณ์ ๋ณด๋ค ๊ฐ๋ ์ฑ์๊ณ ์ฌ์ฌ์ฉ์ฑ์ด ๋์ ํผ๋์ ์์ฑํ์๋ค.
ํผ๋์ ์์ฑํ์์ง๋ง ํ ๊ฐ์ง ๋ถํธํ ์ ์ด ์กด์ฌํ๋๋ฐ, ํ์ฌ ์ฝ๋๋ ๋จ์ผ URL์ด๋ผ step ์ฌ์ด์ ๋ค๋ก๊ฐ๊ธฐ, ์์ผ๋ก๊ฐ๊ธฐ ์ง์์ด ์ ๋๋ ๋ถํธํจ์ด ์กด์ฌํ๋ค.
์ด๋ฅผ router์ shallow push API๋ฅผ ์ฌ์ฉํด ์ฟผ๋ฆฌํ๋ผ๋ฏธํฐ๋ฅผ ์ ๋ฐ์ดํธํด์ค์ ๊ฐ๋ฅํ๋๋ก ๊ตฌํํ ์๋ ์๋ค.
ย
๋ด ์ฝ๋์ ์ ์ฉํ๊ธฐ
์ปค๋จธ์ค ํ๋ซํผ์ ์ฌ์ฉํ๋ฉด ์ฃผ๋ฌธ ๊ฒฐ์ ๊ณผ์ ์ ๋ฐ๋์ ๊ฑฐ์น๊ฒ ๋๋ค.
ํ์๋ ์ปค๋จธ์ค ํ๋ซํผ ํ๋ก์ ํธ์์ ํ ์ค์ ํผ๋ ํจํด์ ์ ์ฉํด ๊ฒฐ์ ๊ณผ์ ์ฆ, ๊ฒฐ์ ํผ๋์ ์ ์ฉํ์๋ค.
์ฅ๋ฐ๊ตฌ๋์ ๋ด๊ฒจ์ง ์ํ์ ์ฃผ๋ฌธํ๋ฉด ๋ฐฐ์ก์ง ์ฃผ์ > ์ฃผ๋ฌธ ๋ชฉ๋ก, ๊ฒฐ์ ์๋ฃ ์ 3๋จ๊ณ๋ฅผ ๊ฑฐ์น๊ฒ ๋๋ค.
๊ฐ๋จํ๊ณ ์ฌํํ ๊ฒฐ์ ๊ณผ์ ์ธ๋ฐ ๊ตณ์ด ํผ๋ ํจํด์ ์ ์ฉํ ์ด์ ๋ ์ฝ๋ ๋ฆฌํํ ๋ง์ ์ํํ๋ฉด์ ์ ์ฐํ ์ ์ง๋ณด์๊ฐ ๊ฐ๋ฅํ๋๋ก ํ๊ธฐ ์ํด ํผ๋์ ์ ์ฉํ์๋ค.
์ฒ์์ผ๋ก ๊ฒฐ์ ํ๋ก์ธ์ค๋ฅผ ๊ตฌํํด ๋ณด์๊ธฐ ๋๋ฌธ์ ์ธ์ ๋ ์ง ๊ฒฐ์ ํ๋ก์ธ์ค๋ ๋ณ๊ฒฝ๋ ์ ์์ผ๋ฉฐ, ๋ํ ์ธ์ ๋ ์ง ์ ์ฐํ๊ฒ ์ฝ๋ ์์ ๋ฐ ์คํ์ผ ์์ ์ด ๊ฐ๋ฅํ๋๋ก ํ๊ธฐ ์ํด ํผ๋ ํจํด์ ํ๋ก์ ํธ์ ์ ์ฉํด ๋ณด์๋ค.
useFunnel
import { ReactElement, ReactNode, useState } from 'react';
export interface StepProps {
name: string;
children: ReactNode;
}
export interface FunnelProps {
children: Array<ReactElement<StepProps>>;
}
export const useFunnel = (defaultStep: string) => {
const [step, setStep] = useState(defaultStep);
// Step
const Step = (props: StepProps): ReactElement => {
return <>{props.children}</>;
};
// Funnel
const Funnel = ({ children }: FunnelProps) => {
const targetStep = children.find((childStep) => childStep.props.name === step);
return <>{targetStep}</>;
};
const nextClickHandler = (nextStep: string) => {
setStep(nextStep);
}
const prevClickHandler = (prevStep: string) => {
setStep(prevStep);
}
return {
Funnel,
Step,
currentStep: step,
nextClickHandler,
prevClickHandler
} as const;
};
const { Funnel, Step, nextClickHandler, prevClickHandler, currentStep } = useFunnel(steps[0].name);
๋ถ๋ชจ ์ปดํผ๋ํธ์์ useFunnel์ ์ ์ธํด ์ฃผ์ด ์์ ์ปดํฌ๋ํธ props๋ก ๊ฐ์ ์ ๋ฌํ์ฌ ๋ชจ๋ ์ํ ๊ด๋ฆฌ ๋ฐ ํจ์๋ ์ต์์ ๋ถ๋ชจ ์ปดํฌ๋ํธ์์ ๊ด๋ฆฌํด ์ฃผ๋๋ก ํ์๋ค.
๋ฐ๋ก ์๋ ์์ ์ปดํฌ๋ํธ ์ฝ๋๊ฐ ํผ๋์ ์ ์ฉํ ์ปดํฌ๋ํธ ์ฝ๋์ด๋ค.
Funnel ์ ์ฉ
export const OrderSetup = (
{
steps,
Funnel,
Step,
nextClickHandler,
prevClickHandler,
order,
onSetOrder
}: OrderSetupProps) => {
return (
<>
<Funnel>
<Step name="๋ฐฐ์ก์ง์
๋ ฅ">
<OrderAddress
onNext={() => nextClickHandler(steps[1].name)}
onSetOrder={onSetOrder}
order={order}
/>
</Step>
<Step name="์ฃผ๋ฌธ๋ชฉ๋กํ์ธ">
<OrderList
onPrev={() => prevClickHandler(steps[0].name)}
onNext={() => nextClickHandler(steps[2].name)}
order={order}
/>
</Step>
<Step name="๊ฒฐ์ ์๋ฃ">
<OrderResult />
</Step>
</Funnel>
</>
)
}
ย
๋ง์น๋ฉฐ
ํ ์ค์ ์ฝ๋๋ฅผ ํ๋ด๋ด์ด ํผ๋์ ๊ฐ๋ ์ ์ ์ฉํด ๋ณด์์ง๋ง, ์ปค์คํ ํ ๊ณผ ํฉ์ฑ ์ปดํฌ๋ํธ๋ฅผ ๊ตฌํํ๋ฉด์ ์ถ์ํ์ ๋ํ ๊ณ ๋ฏผ๋ ํ์ธต ๋์๋ค.
๋ํ ๊ฒฐ์ ํ๋ก์ธ์ค์ ์ถ์ํ ๋ฐ ์์ง๋์ ๋ํ POC ๊ณผ์ ๋ ์ด๋ฒ ํ ์ค ํผ๋ ํจํด์ ํตํด ํฐ ๋์์ด ๋์๋ค.
๋์ผ๋ก ์ง์ ๋ฆผ๋๊ณผ์ ํ๋ผ์ด๋น ๋คํฌ์ํน์ ์ ์ฒญํด ๋ณด์๋๋ฐ, ๊ธฐํ๊ฐ ๋๋ค๋ฉด ๊ผญ ํ๋ฒ ๋ง๋ ๋ต๊ณ ์ถ๋ค. ๋งํฌ
๊ฐ์ฌํฉ๋๋ค.



Top comments (0)