DEV Community

Cover image for Toss์˜ ํผ๋„(Funnel) ํŒจํ„ด ์ ์šฉํ•ด๋ณด๊ธฐ
Heetae Kim
Heetae Kim

Posted on • Edited on

Toss์˜ ํผ๋„(Funnel) ํŒจํ„ด ์ ์šฉํ•ด๋ณด๊ธฐ

"ํ† ์Šค SLASH 23์˜ ํผ๋„: ์Ÿ์•„์ง€๋Š” ํŽ˜์ด์ง€ ๊ด€๋ฆฌํ•˜๊ธฐ" ๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๋งํฌ

ํ† ์Šค์—์„œ๋Š” ๋‹ค๋ฅธ ํ†ต์‹ ์‚ฌ๋“ค ์ฒ˜๋Ÿผ ์š”๊ธˆ์ œ ๊ฐ€์ž… ์‹ ์ฒญ์„œ๋ฅผ ๋ฐ›๊ณ ์žˆ๋‹ค.
์ฐจ๋ณ„ํ™”๋œ ์ ์€ ํ•œ ํŽ˜์ด์ง€๋กœ ์ด๋ฃจ์–ด์ง„ ํผ ๋Œ€์‹ , ํ•œ ํŽ˜์ด์ง€์— ํ•œ ํ•ญ๋ชฉ๋งŒ ์ œ์ถœํ•˜๋Š” UI๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.

ํ•˜์ง€๋งŒ ์ด๋Ÿฐ์‹์œผ๋กœ ๋งŽ์€ ํŽ˜์ด์ง€๋“ค์„ ํ•œ ๋ฒˆ์— ๊ด€๋ฆฌํ•˜๊ธฐ๋ž€ ์‰ฝ์ง€ ์•Š๋‹ค.
์œ„ ๋ฐœํ‘œ์—์„œ๋Š” ์ด๋Ÿฌํ•œ ํŽ˜์ด์ง€๋“ค์„ ํšจ๊ณผ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„œ ์„ค๋ช…ํ•˜๊ณ  ์žˆ๋‹ค.

ย 

ํผ๋„(Funnel)

ํผ๋„์˜ ์‚ฌ์ „์ ์ธ ๋œป์ธ ๊น”๋Œ€๊ธฐ ์™€ ๊ฐ™์€ ๋ชจ์–‘์„ ๋ ๊ธฐ ๋•Œ๋ฌธ์— ๋ถ™์—ฌ์ง„ ์ด๋ฆ„์ด๋ผ๊ณ  ํ•œ๋‹ค.
์ด๋Ÿฌํ•œ ๊น”๋Œ€๊ธฐ ๋ชจ์–‘์ธ ํผ๋„์„ ํ† ์Šค์˜ ํšŒ์›๊ฐ€์ž… ์ ˆ์ฐจ์— ์ ์šฉ๋˜์–ด ์žˆ๋‹ค๊ณ  ํ•œ๋‹ค.

Image description

Image description

๊ทธ๋ ‡๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ํ”Œ๋กœ์šฐ๋ฅผ ๊ด€๋ฆฌํ• ์ง€ ๊ถ๊ธˆํ•œ๋ฐ, ํ† ์Šค์—์„œ๋Š” ๋‘ ๊ฐ€์ง€ ์˜ˆ์‹œ๋ฅผ ๋“ค์–ด์ฃผ์—ˆ๋‹ค.

๊ธฐ์กด ํผ๋„ ๋ฐฉ์‹

์™„๋ฃŒ ๋ฒ„ํŠผ์„ ์ด์šฉํ•ด router๋ฅผ ์ด๋™ํ•˜๋ฉด์„œ ํŽ˜์ด์ง€๋งˆ๋‹ค ์ˆ˜์ง‘ํ•˜๋Š” ์œ ์ €์˜ ์ •๋ณด๋ฅผ ์ „์—ญ์ƒํƒœ์— ์ €์žฅํ•˜๋ฉฐ ๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€์—์„œ api๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์ •์„์ ์ธ ๊ตฌํ˜„๋ฐฉ๋ฒ•

Image description

ํ† ์Šค ํผ๋„ ๋ฐฉ์‹

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>
)


Enter fullscreen mode Exit fullscreen mode

ย 

๊ธฐ์กด ํผ๋„์˜ ์•„์‰ฌ์šด ์  ๋ฐ ๋ณด์™„

๊ธฐ์กด ํผ๋„ ๋ฐฉ์‹๋„ ์™„๋ฒฝํ•ด ๋ณด์ด์ง€๋งŒ ๋ช‡ ๊ฐ€์ง€ ์•„์‰ฌ์šด์ ์ด ์žˆ๋‹ค.

  1. ํŽ˜์ด์ง€ ํ๋ฆ„์ด ํฉ์–ด์ ธ์žˆ๋‹ค.
    ๊ฐ€์ž… ํ”Œ๋กœ์šฐ๋ฅผ ํŒŒ์•…ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” 3๊ฐœ์˜ ์ปดํฌ๋„ŒํŠธ ํŒŒ์ผ์„ ๋„˜๋‚˜๋“ค์–ด์•ผ ํ•œ๋‹ค.

  2. ํ•œ ๊ฐ€์ง€ ๋ชฉ์ ์„ ์œ„ํ•œ ์ƒํƒœ๊ฐ€ ํฉ์–ด์ ธ ์žˆ๋‹ค.
    ์ƒํƒœ๋ฅผ ์ˆ˜์ง‘ํ•˜๋Š” ๊ณณ๊ณผ ์‚ฌ์šฉํ•˜๋Š” ๊ณณ์ด ๋‹ฌ๋ผ์„œ, 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>
)


Enter fullscreen mode Exit fullscreen mode
  1. useState๋ฅผ ์ด์šฉํ•ด ์ง€์—ญ์ƒํƒœ๋ฅผ ๋งŒ๋“ค์–ด์ฃผ๊ณ  ๊ฐ€์ž…๋ฐฉ์‹, ์ฃผ๋ฏผ๋ฒˆํ˜ธ ๋“ฑ ํ˜„์žฌ ์–ด๋А UI๋ฅผ ๋ณด์—ฌ์ค˜์•ผ ํ•˜๋Š”์ง€ state์— ์ €์žฅํ•œ๋‹ค.

  2. ๊ทธ๋Ÿฌ๋ฉด ํ•˜๋‚˜์˜ ์ปดํฌ๋„ŒํŠธ ํŽ˜์ด์ง€์—์„œ ํฉ์–ด์ ธ์žˆ๋Š” ํŽ˜์ด์ง€๋“ค์„ ํ•œ ํŽ˜์ด์ง€์— ์‘์ง‘์‹œ์ผœ ๊ด€๋ฆฌ๊ฐ€ ์šฉ์ดํ•˜๊ฒŒ ๋งŒ๋“ค์–ด ์ค„ ์ˆ˜ ์žˆ๋‹ค.

  3. ๊ทธ๋ฆฌ๊ณ  step ์ƒํƒœ์— ๋”ฐ๋ผ ๊ฐ UI ์ปดํฌ๋„ŒํŠธ๋ฅผ ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋งํ•˜๊ณ , '๋‹ค์Œ' ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅผ ๋•Œ step ์ƒํƒœ๋ฅผ ์›ํ•˜๋Š” UI๋กœ ์—…๋ฐ์ดํŠธ ํ•œ๋‹ค.

  4. ๊ฒฐ๊ณผ์ ์œผ๋กœ 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>
)


Enter fullscreen mode Exit fullscreen mode

๊ฐ step์„ ์ปดํฌ๋„ŒํŠธ๋กœ ๋”ฐ๋กœ ๋งŒ๋“ค์–ด ์ฃผ์–ด ์ค‘๋ณต๋œ step ์ฒ˜๋ฆฌ๋ฅผ ์ถ”์ƒํ™” ํ•ด์ค€๋‹ค.

๊ทธ๋ฆฌ๊ณ  props๋ฅผ ์กฐ๊ธˆ ๋” ๊น”๋”ํ•˜๊ฒŒ ์ž‘์„ฑํ•ด ์ค„ ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค.



const [registerData, setRegisterData] = useState()
const [step, setStep] = useState<"๊ฐ€์ž…๋ฐฉ์‹"|"์ฃผ๋ฏผ๋ฒˆํ˜ธ"|"์ง‘์ฃผ์†Œ"|"๊ฐ€์ž…์„ฑ๊ณต">("๊ฐ€์ž…๋ฐฉ์‹")

return (
  <main>
    <Step name="๊ฐ€์ž…๋ฐฉ์‹">
      <๊ฐ€์ž…๋ฐฉ์‹ onNext={() => setStep("์ฃผ๋ฏผ๋ฒˆํ˜ธ")} />
    </Step>
    <Step name="์ฃผ๋ฏผ๋ฒˆํ˜ธ">
      <์ฃผ๋ฏผ๋ฒˆํ˜ธ onNext={() => setStep("์ง‘์ฃผ์†Œ")} />
    </Step>
   // ...
  </main>
)


Enter fullscreen mode Exit fullscreen mode

์˜ˆ์ œ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด ์กฐ๊ฑด๋ฌธ์„ ์‚ญ์ œํ•˜๊ณ  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]
}


Enter fullscreen mode Exit fullscreen mode


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>
)


Enter fullscreen mode Exit fullscreen mode

์—ฌ๊ธฐ๊นŒ์ง€ ํผ๋„์˜ ์‘์ง‘๋„์™€ ์ถ”์ƒํ™”๋ฅผ ๊ฑฐ์ณ์„œ ๋ณด๋‹ค ๊ฐ€๋…์„ฑ์žˆ๊ณ  ์žฌ์‚ฌ์šฉ์„ฑ์ด ๋†’์€ ํผ๋„์„ ์™„์„ฑํ•˜์˜€๋‹ค.

ํผ๋„์€ ์™„์„ฑํ•˜์˜€์ง€๋งŒ ํ•œ ๊ฐ€์ง€ ๋ถˆํŽธํ•œ ์ ์ด ์กด์žฌํ•˜๋Š”๋ฐ, ํ˜„์žฌ ์ฝ”๋“œ๋Š” ๋‹จ์ผ 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;
};


Enter fullscreen mode Exit fullscreen mode


const { Funnel, Step, nextClickHandler, prevClickHandler, currentStep } = useFunnel(steps[0].name);


Enter fullscreen mode Exit fullscreen mode

๋ถ€๋ชจ ์ปดํผ๋„ŒํŠธ์—์„œ 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>
    </>
  )
}


Enter fullscreen mode Exit fullscreen mode

ย 

๋งˆ์น˜๋ฉฐ

ํ† ์Šค์˜ ์ฝ”๋“œ๋ฅผ ํ‰๋‚ด๋‚ด์–ด ํผ๋„์˜ ๊ฐœ๋…์„ ์ ์šฉํ•ด ๋ณด์•˜์ง€๋งŒ, ์ปค์Šคํ…€ ํ›…๊ณผ ํ•ฉ์„ฑ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด์„œ ์ถ”์ƒํ™”์— ๋Œ€ํ•œ ๊ณ ๋ฏผ๋„ ํ•œ์ธต ๋Š˜์—ˆ๋‹ค.

๋˜ํ•œ ๊ฒฐ์ œ ํ”„๋กœ์„ธ์Šค์˜ ์ถ”์ƒํ™” ๋ฐ ์‘์ง‘๋„์— ๋Œ€ํ•œ POC ๊ณผ์ •๋„ ์ด๋ฒˆ ํ† ์Šค ํผ๋„ ํŒจํ„ด์„ ํ†ตํ•ด ํฐ ๋„์›€์ด ๋˜์—ˆ๋‹ค.

๋์œผ๋กœ ์ง„์œ ๋ฆผ๋‹˜๊ณผ์˜ ํ”„๋ผ์ด๋น— ๋„คํฌ์›Œํ‚น์„ ์‹ ์ฒญํ•ด ๋ณด์•˜๋Š”๋ฐ, ๊ธฐํšŒ๊ฐ€ ๋œ๋‹ค๋ฉด ๊ผญ ํ•œ๋ฒˆ ๋งŒ๋‚˜ ๋ต™๊ณ  ์‹ถ๋‹ค. ๋งํฌ

๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

Top comments (0)