Project๐Ÿ› ๏ธโš™๏ธ

SW ์บก์Šคํ†ค ํ”„๋กœ์ ํŠธ <๋‹จํ†ต> ํ”„๋ก ํŠธ์—”๋“œ ํšŒ๊ณ 

Gaeun Lee 2024. 7. 8. 23:07

์•ˆ๋…•ํ•˜์„ธ์š”.

์ด๋ฒˆ์— ์†Œํ”„ํŠธ์›จ์–ดํ•™๊ณผ 4-1ํ•™๊ธฐ ์บก์Šคํ†ค ํ”„๋กœ์ ํŠธ๋ฅผ ๋งˆ์น˜๋ฉฐ ํšŒ๊ณ ๋ก์„ ๋‚จ๊ธฐ๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

์ด ๊ธ€์„ ํ†ตํ•˜์—ฌ ๋ฟŒ๋“ฏํ•˜๊ธฐ๋„ ํ•˜์ง€๋งŒ ์•„์‰ฌ์› ๋˜ <๋‹จํ†ต> ํ”„๋กœ์ ํŠธ๋ฅผ ํšŒ๊ณ ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

์„œ๋น„์Šค ์†Œ๊ฐœ

๋‹จํ†ต์€ ํ•™์šฐ๋“ค์˜ ํ•™์ƒํšŒ ํ–‰์‚ฌ ์ฐธ์—ฌ๋ฅผ ์ฆ์ง„ํ•˜๊ณ , ํ•™์ƒํšŒ์˜ ํผ ๊ฐ€๊ณต ์—…๋ฌด๋ฅผ ๊ฐ„ํŽธํ™”ํ•ด์ฃผ๋Š” ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค.

ํ•™์ƒํšŒ๋Š” ๋‹จํ†ต์„ ํ†ตํ•˜์—ฌ ํ•™์ƒํšŒ ํ–‰์‚ฌ๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ํ™๋ณดํ•˜๊ณ , ํ•™์šฐ๋Š” ํ–‰์‚ฌ ์ฐธ์—ฌ ์ •๋ณด, ์ง„ํ–‰ ์ƒํ™ฉ, ํ–‰์‚ฌ ์ผ์ • ๋“ฑ์„ ํŽธ๋ฆฌํ•˜๊ฒŒ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹จํ†ต์€ ํ•™์šฐ๋“ค์ด ํ–‰์‚ฌ ํผ์„ ์ œ์ถœํ•  ๋•Œ ๋ฒˆ๊ฑฐ๋กœ์› ๋˜ ์žฌํ•™ ์ธ์ฆ ์ •๋ณด (๋‹จ๊ตญ๋Œ€ํ•™๊ต ์›น์ •๋ณด ํ™”๋ฉด ์บก์ณ, ์ „๊ณต๊ณผ ํ•™๋ฒˆ ์ž…๋ ฅ ๋“ฑ) ๊ธฐ์ž… ์ ˆ์ฐจ๋ฅผ ํšŒ์›๊ฐ€์ž…์‹œ ๋‹จ ํ•œ ๋ฒˆ์˜ ํ•™๊ต ์ด๋ฉ”์ผ ์ธ์ฆ์œผ๋กœ ๋Œ€์ฒดํ•˜๊ณ , ํผ ์‘๋‹ต ๋ฆฌ์ŠคํŠธ๋ฅผ ์—‘์…€๋กœ ์ถ”์ถœํ•˜๋Š” ๊ธฐ๋Šฅ์„ ํ†ตํ•˜์—ฌ ํ•™์ƒํšŒ์˜ ์—…๋ฌด ์ ˆ์ฐจ๋ฅผ ๊ฐ„์†Œํ™”ํ•ฉ๋‹ˆ๋‹ค!

์ด ํ”„๋กœ์ ํŠธ๋Š” ํ•™์ƒํšŒ์˜ ๋„์›€์„ ๊ตฌํ•˜์—ฌ ์‹ค์ œ ํ•™์šฐ๋ถ„๋“ค๊ป˜ ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์„ ๋ชฉํ‘œ๋กœ ์ง„ํ–‰ํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

 

 

 

 

ํ™”๋ฉด ๊ตฌ์ƒ

ํ”ผ๊ทธ๋งˆ๋กœ ํ™”๋ฉด์„ ์ง์ ‘ ๋งŒ๋“ค๊ณ  ๋งŽ์€ ๋ ˆํผ๋Ÿฐ์Šค๋“ค์„ ๋ณด๋ฉฐ, ๋””์ž์ธ์˜ ์ผ๊ด€์„ฑ, ์‚ฌ์šฉ์ž ์ ‘๊ทผ์„ฑ ๋“ฑ UI/UX์— ๋Œ€ํ•œ ๊นŠ์ด ์žˆ๋Š” ๊ณ ๋ฏผ์„ ํ•  ์ˆ˜ ์žˆ์—ˆ๋Š”๋ฐ์š”!

ํŠนํžˆ ๊ฐ„ํŽธํ•œ ํšŒ์›๊ฐ€์ž… ์ ˆ์ฐจ๋ฅผ ์ œ๊ณตํ•˜๊ณ ์ž ํผ๋„์„ ์•ผ์‹ฌ์ฐจ๊ฒŒ ๋„์ž…ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ใ…Žใ…Ž

 

 

 

 

 

๊ธฐ์ˆ  ์Šคํƒ ์„ ์ •

๋‹จํ†ต์€ ์ฃผ๋กœ ์Šค๋งˆํŠธํฐ์œผ๋กœ ํ–‰์‚ฌ ๋‚ด์šฉ์„ ํ™•์ธํ•˜๋Š” ํ•™์šฐ๋“ค์„ ์œ„ํ•˜์—ฌ ๋ชจ๋ฐ”์ผ ๊ธฐ์ค€์˜ UI๋ฅผ ์ œ๊ณตํ•˜์˜€์Šต๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์•Œ๋ฆผ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•˜์—ฌ ๋„ค์ดํ‹ฐ๋ธŒ ์•ฑ๊ณผ PWA ์ค‘ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ค์น˜๋ฅผ ์š”๊ตฌํ•˜์ง€ ์•Š๊ณ  ํ”Œ๋žซํผ์˜ ์ œ์•ฝ์ด ์—†๋Š” PWA๋ฅผ ์„ ํƒํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Œ€์‹  ์›น์œผ๋กœ ๋„ค์ดํ‹ฐ๋ธŒ ์•ฑ๋งŒํผ์˜ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์„ ์‚ฌํ•˜๊ธฐ ์œ„ํ•ด์„œ, SPA๋ฅผ ์ง€์›ํ•˜๋Š” React.js๋ฅผ ํ”„๋ก ํŠธ์—”๋“œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ์ฑ„ํƒํ•˜์˜€๊ณ , ํƒ€์ž… ์ •์˜์™€ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ†ตํ•˜์—ฌ ์œ ์ง€๋ณด์ˆ˜, ํ™•์žฅ์„ฑ, ์ผ๊ด€์„ฑ๋ฅผ ์ œ๊ณตํ•˜๋Š” Typescript๋กœ ์ž‘์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค.

๋‹จํ†ต์€ ์ดˆ๊ธฐ ์›น ๋ Œ๋”๋ง ์†๋„ ๊ฐ์ถ•์„ ์œ„ํ•˜์—ฌ ๋ Œ๋”๋ง ์„ฑ๋Šฅ ์ด์Šˆ๊ฐ€ ์žˆ๋Š” CSS-in-JS ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(Styled Components, Emotion)๊ฐ€ ์•„๋‹Œ, Utility First ๋ฐฉ์‹์„ ์ฑ„ํƒํ•œ Tailwind CSS๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค. Tailwind CSS๋Š” ๋›ฐ์–ด๋‚œ ๊ฐœ๋ฐœ ์ƒ์‚ฐ์„ฑ์„ ์ œ๊ณตํ•œ๋‹ค๋Š” ์ ์—์„œ๋„ ํฐ ์ด์ ์„ ์ง€๋‹™๋‹ˆ๋‹ค.

๋งˆ์ง€๋ง‰์œผ๋กœ ๋ฐ์ดํ„ฐ ํŒจ์นญ, ์บ์‹ฑ, ๋™๊ธฐํ™” ๋“ฑ ์„œ๋ฒ„ ์ƒํƒœ ๊ด€๋ฆฌ ์ž‘์—…์„ ๊ฐ„๋‹จํ•˜๊ณ  ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋Š” React Query๋ฅผ ๋„์ž…ํ•˜์—ฌ, ํด๋ผ์ด์–ธํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์„ฑ๋Šฅ๊ณผ ๊ฐœ๋ฐœ ์ƒ์‚ฐ์„ฑ์„ ํ–ฅ์ƒํ•˜๊ณ ์ž ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

 

 

 

 

๊ตฌํ˜„

(1) ํผ๋„

ํผ๋„์€ ๋ ˆ์ด์•„์›ƒ์˜ children props๊ฐ€ ReactNode๋“ค์„ ๋ฐฐ์—ด ์š”์†Œ๋กœ ๊ฐ€์ง„๋‹ค๋Š” ์•„์ด๋””์–ด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ, SignUpLayout ์•ˆ์— ํ•˜๋‚˜์˜ children ์š”์†Œ๋ฅผ ์ธ๋ฑ์Šค๋กœ ์ง€์ •ํ•˜์—ฌ(children[index]) ํ•˜๋‚˜์˜ ์ปดํฌ๋„ŒํŠธ๋งŒ ๋‚˜ํƒ€๋‚˜๋Š” ๋กœ์ง์„ ๊ตฌ์ƒํ•˜์˜€์Šต๋‹ˆ๋‹ค.

SignUpLayout children์˜ ์ธ๋ฑ์Šค์ธ ํผ๋„์˜ ๋‹จ๊ณ„(step)๋Š” ์ˆซ์ž๋กœ ํ‘œํ˜„ํ•˜์˜€๊ณ , pathname์ด ๋ฐ”๋€” ๋•Œ๋งˆ๋‹ค pathname์—์„œ steps์— ์†ํ•œ pageName์„ ๊ฐ€์ ธ์™€ steps.findIndex๋ฅผ ํ†ตํ•˜์—ฌ pageName์˜ index๋ฅผ ์ฐพ์•„ step์œผ๋กœ ์—…๋ฐ์ดํŠธํ•œ ๋‹ค์Œ, SignUpLayout์— step props๋กœ ์ „๋‹ฌํ•˜์˜€์Šต๋‹ˆ๋‹ค.

ํผ๋„์˜ ํ๋ฆ„์„ ์‰ฝ๊ฒŒ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด ํ˜„์žฌ step, ์Šคํ…๋ณ„ ์ปดํฌ๋„ŒํŠธ๋“ค์„ ํ•˜๋‚˜์˜ ํŒŒ์ผ์—์„œ(SignupPage) ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์Šคํ… ์ปดํฌ๋„ŒํŠธ์˜ onNext props์„ ํ†ตํ•˜์—ฌ ์Šคํ…๋ณ„ ๋‹ค์Œ ์ด๋ฒคํŠธ๋„ ํ•œ ๋ฒˆ์— ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

SignUpPage.tsx

export const SignUpPage = () => {
  const navigate = useNavigate();
  const { mutate: postSignUpInfo } = usePostSignUpInfo();
  const { pathname } = useLocation();
  const [step, setStep] = useState(0);
  const steps = ['email', 'info', 'password', 'success'];
  useEffect(() => {
    const pageName = pathname.split('?')[0].split('/sign-up/')[1];
    setStep(steps.findIndex((item) => item === pageName));
  }, [pathname]);

  const submitSignUpInfo = () => {
    postSignUpInfo();
  };

  return (
    <SignUpLayout step={step}>
      <MailEntryPage />
      <InfoEntryPage onNext={() => navigate('/sign-up/password')} />
      <PasswordEntryPage onNext={() => submitSignUpInfo()} />
      <SignUpSuccessPage onNext={() => navigate('/login')} />
    </SignUpLayout>
  );
};

 

SignUpLayout.tsx

export const SignUpLayout = ({
  children,
  step,
}: {
  children: ReactNode[];
  step: number;
}) => {
  const { pathname } = useLocation();
  const isInSuccessPage = pathname === '/sign-up/success';

  return (
    <div
      className={`pwa-layout grid h-full w-full gap-8 p-8 ${isInSuccessPage && 'gradientBackground'}`}
    >
      {children[step]}
    </div>
  );
};

 

 

(2) input ์ž๋™ ํฌ์ปค์Šค

ํšŒ์›๊ฐ€์ž…์˜ ํ•ต์‹ฌ, ์–ผ๋งˆ๋‚˜ ๊ฐ„ํŽธํ•œ Input ์ž…๋ ฅ์„ ์ œ๊ณตํ• ์ง€์— ๋Œ€ํ•ด์„œ๋„ ๊ณ ๋ฏผํ•ด๋ดค๋Š”๋ฐ์š”.

์—ฌ๋Ÿฌ ํŽ˜์ด์ง€์— ๊ฑฐ์ณ ์ž…๋ ฅ๋˜๋Š” ํšŒ์› ์ •๋ณด๋ฅผ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•˜์—ฌ ํšŒ์› ์ •๋ณด๋Š” zustand๋ฅผ ํ†ตํ•ด ์ „์—ญ ์ƒํƒœ๋กœ ๊ด€๋ฆฌํ•˜์˜€์Šต๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  input ์ž…๋ ฅ ํ›„ ์—”ํ„ฐ๋ฅผ ๋ˆ„๋ฅผ ๋•Œ๋งˆ๋‹ค ์ƒˆ๋กœ์šด input์ด ์œ„์—์„œ ํ‘œ์‹œ๋˜๋Š” ๊ตฌ์กฐ์ด๋ฏ€๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋กœ์ง์œผ๋กœ ๊ตฌํ˜„ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

1. input ์ปดํฌ๋„ŒํŠธ๋“ค์˜ ์ •๋ณด๋ฅผ ๋‹ด์€ ๋ฐฐ์—ด์ธ inputAttrList๋ฅผ ์ •์˜
2. ์—”ํ„ฐ ์ž…๋ ฅ์‹œ ์ƒˆ๋กญ๊ฒŒ ๋ณด์—ฌ์ง€๋Š” input์˜ index์ธ activatedIndex์„ 0์œผ๋กœ ์ดˆ๊ธฐํ™”ํ•œ๋‹ค.
3. inputAttrList๋ฅผ slice(0, activatedIndex + 1).reverse().map() ํ•˜์—ฌ inputAttrList๋ฅผ activatedIndex๊นŒ์ง€๋งŒ ์ž๋ฅด๊ณ , ์ด๋ฅผ ๊ฑฐ๊พธ๋กœ ๋‚˜์—ดํ•œ ํ›„ ํ™”๋ฉด์— ๋งตํ•‘ํ•œ๋‹ค.
4. ์—”ํ„ฐ ์ž…๋ ฅ์‹œ activatedIndex๋ฅผ 1์„ ๋”ํ•œ๋‹ค.

 

 

์ด๋Š” info ํŽ˜์ด์ง€์™€ password ํŽ˜์ด์ง€์—์„œ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•˜๋ฏ€๋กœ ๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ InputContainer๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ํŽ˜์ด์ง€๋ณ„ state๋กœ activatedIndex๋ฅผ ๊ด€๋ฆฌํ•˜๋ฉด ํŽ˜์ด์ง€๊ฐ€ ์ „ํ™˜๋  ๋•Œ๋งˆ๋‹ค activatedIndex๊ฐ€ 0์œผ๋กœ ์ดˆ๊ธฐํ™”๋˜๋ฏ€๋กœ, ์ด์ „ ํŽ˜์ด์ง€๋กœ ์ด๋™์‹œ ์ž…๋ ฅ ๋‚ด์šฉ์„ ๋ณผ ์ˆ˜ ์—†๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ activatedIndex๋ฅผ info์™€ password ํ”„๋กœํผํ‹ฐ๋ฅผ ๊ฐ€์ง„ ์ „์—ญ ์ƒํƒœ์˜ ๊ฐ์ฒด๋กœ ์ •์˜ํ•˜์—ฌ, ๊ฐ ํŽ˜์ด์ง€์˜ ํ™œ์„ฑํ™”๋œ input ์ธ๋ฑ์Šค๋ฅผ ๋”ฐ๋กœ ์ €์žฅํ•˜๋„๋ก ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

InputContainer์—๋Š” ๋‹ค์Œ์˜ props๋ฅผ ๋ถ€์—ฌํ•˜์—ฌ info, password ํŽ˜์ด์ง€์—์„œ ๋ชจ๋‘ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋„๋ก ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

  inputAttrList: userInfoInputAttr[];
  containerID: 'info' | 'password';
  onNext: () => void;

 

 

input์ด ํ‘œ์‹œ๋˜๋Š” ๊ฒƒ์„ ๊ตฌํ˜„ํ•˜์˜€์œผ๋ฏ€๋กœ, ์ด์ œ input์— ์ž๋™ focus ๋˜๋„๋ก ํ•ด์•ผ ํ•˜๋Š”๋ฐ์š”.

InputContainer์— inputContainerRef๋ฅผ ์ „๋‹ฌํ•œ ํ›„, useEffect๋ฅผ ํ†ตํ•˜์—ฌ activatedInputIndex ๊ฐ’์ด ๋ฐ”๋€” ๋•Œ๋งˆ๋‹ค DOM์—์„œ inputContainerRef.current์˜ ChildNodes ๋ฐฐ์—ด(Input ์ปดํฌ๋„ŒํŠธ ์š”์†Œ๋กœ ๊ฐ€์ง) ์ค‘ 0๋ฒˆ์งธ ์ธ๋ฑ์Šค(์ตœ์ƒ์œ„์— ์œ„์น˜ํ•œ Input ์ปดํฌ๋„ŒํŠธ)๋ฅผ focusํ•˜๋„๋ก ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

  useEffect(() => {
    const currentInput =
      inputContainerRef.current?.children[0]?.querySelector('input');
    currentInput?.focus();
  }, [activatedInputIndex]);

Enter ๋ˆ„๋ฅผ์‹œ ์ž๋™์œผ๋กœ input์ด ํ‘œ์‹œ๋˜๋ฉฐ focus๊ฐ€ ์—…๋ฐ์ดํŠธ ๋ฉ๋‹ˆ๋‹ค.

 

 

 

(3) ํผ ์ƒ์„ฑ

๋ณต์žกํ•œ ๊ฐ์ฒด state์— ํƒ€์ž… ๊ฐ€๋“œ๋ฅผ ์ ์šฉํ•˜๊ณ , input ์ด๋ฒคํŠธ๋กœ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•ด์•ผ ํ•ด์„œ ๊ฑฑ์ •๋œ ํŒŒํŠธ์˜€๋Š”๋ฐ์š”.

์ด ๋ถ€๋ถ„์— ๋Œ€ํ•ด์„œ๋Š” trouble shooting์„ ๊ธฐ๋กํ•˜๊ธฐ๋„ ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

[React/TypeScript] Input์˜ event.target.value์„ string์ด ์•„๋‹Œ ํŠน์ • ํƒ€์ž…์œผ๋กœ ๋ช…์‹œํ•  ์ˆ˜ ์žˆ์„๊นŒ?

 

ํผ ์ƒ์„ฑ ํŽ˜์ด์ง€๋Š” ์งˆ๋ฌธ์˜ ๋‚ด์šฉ, ์ƒ์„ธ ์„ค๋ช…, ์œ ํ˜•, ์˜ต์…˜ ๋ฆฌ์ŠคํŠธ ๋“ฑ์˜ ํ”„๋กœํผํ‹ฐ๋ฅผ ์ง€๋‹Œ ์งˆ๋ฌธ์˜ ๋ฆฌ์ŠคํŠธ๋ฅผ ํ•˜๋‚˜์˜ state๋กœ ๊ด€๋ฆฌํ•˜์˜€๋Š”๋ฐ์š”.

๋ฆฌ์•กํŠธ์˜ useState๋Š” ๊ฐ์ฒด state๋ฅผ ์—…๋ฐ์ดํŠธ์‹œ ๊ธฐ์กด state ๊ฐ์ฒด์˜ ๋ถˆ๋ณ€์„ฑ์„ ์œ ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋ฏ€๋กœ state์˜ ํŠน์ • ์š”์†Œ๋งŒ ์—…๋ฐ์ดํŠธ์‹œ ์Šคํ”„๋ ˆ๋“œ ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ธฐ์กด ๊ฐ์ฒด๋ฅผ ์ƒˆ๋กœ์šด ๊ฐ์ฒด๋กœ ๋งŒ๋“  ํ›„ ์—…๋ฐ์ดํŠธํ•  ๋‚ด์šฉ์„ ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ด์™€ ๊ฐ™์€ ์ž‘์—…์„ ๊ฑฐ์น˜๋‹ˆ setState ํ•จ์ˆ˜ ์•ˆ์˜ ์ฝ”๋“œ๊ฐ€ ๋งค์šฐ ๋ณต์žกํ•ด์กŒ์Šต๋‹ˆ๋‹ค.

๋‹คํ–‰ํžˆ ํผ ์ƒ์„ฑ ํŽ˜์ด์ง€๋Š” ๊ณ„ํšํ•œ๋Œ€๋กœ ๊ตฌํ˜„์— ์„ฑ๊ณตํ•˜์˜€๊ณ  ๋ฆฌ์•กํŠธ์˜ state ์—…๋ฐ์ดํŠธ ์›๋ฆฌ๋ฅผ ๊นจ์šฐ์ณ ์ข‹์•˜์ง€๋งŒ, ํ•˜๋‚˜์˜ state์— ์—ฌ๋Ÿฌ ๋ฒˆ ์ค‘์ฒฉ๋œ ๋ณต์žกํ•œ ๊ฐ์ฒด๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ  ์ด ๋ณ€ํ™”์— ๋”ฐ๋ผ ํ™”๋ฉด์„ ๋ฆฌ๋ Œ๋”๋งํ•˜๋Š” ๊ฒƒ์€ ์„ฑ๋Šฅ๊ณผ ์œ ์ง€๋ณด์ˆ˜ ์ธก๋ฉด์—์„œ ์ข‹์ง€ ์•Š์•„ ๋ถ„๋ฆฌ ์ž‘์—…์ด ํ•„์š”ํ•  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.๐Ÿฅฒ

 

(4) React Query ๋„์ž…

๋‹จํ†ต์€ ์ฝ”๋“œ ์ค‘๋ณต์„ ๋‚ณ๋Š” ๊ธฐ์กด ๋น„๋™๊ธฐ ํ†ต์‹  ๋กœ์ง์„ React Query๋กœ ๋Œ€์ฒดํ•˜์—ฌ ๊ฐ„ํŽธํ•˜๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์—ˆ๋Š”๋ฐ์š”.

ํŠนํžˆ useInfiniteQuery์„ ํ†ตํ•˜์—ฌ ๋ฌดํ•œ ์Šคํฌ๋กค์„ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ–ˆ๋˜ ์ ์ด ๊ธฐ์–ต์— ๋‚จ์Šต๋‹ˆ๋‹ค.

๋ฌดํ•œ ์Šคํฌ๋กค์€ IntersectionObserver API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์š”์†Œ์™€ ๋ทฐํฌํŠธ์˜ ๊ต์ฐจ์ ์„ ๊ฐ์ง€ํ•˜๋Š” intersection, useInfiniteQuery๋ฅผ ํ†ตํ•˜์—ฌ ๊ฐ€์ ธ์˜จ data์™€ isFetching์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ปค์Šคํ…€ ํ›…์ธ useInfiniteScroll์„ ๋งŒ๋“ค์–ด ๋” ์‰ฝ๊ฒŒ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜์˜€๋‹ต๋‹ˆ๋‹ค.

 

 useInfiniteQuery({
    queryKey: ['infinitePostList'],
    queryFn: ({ pageParam: pageNum }) =>
    Post.getInfinitePostList({ page: pageNum, size, category }),
    initialPageParam: 0,
    getNextPageParam: (lastPage, allPages) =>
     lastPage.length ? allPages.length : undefined,
    gcTime: 0,
  });

 

ํž˜๋“ค์—ˆ๋˜ ์ ์€ useQuery ์‚ฌ์šฉ์‹œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ๋˜์ž๋งˆ์ž ๋ฐ์ดํ„ฐ๊ฐ€ fetch๋˜์–ด, zustand๋กœ ๊ด€๋ฆฌํ•˜๋Š” ์ „์—ญ ์ƒํƒœ์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ fetch๋˜์–ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ, ์ „์—ญ ์ƒํƒœ์˜ ์—…๋ฐ์ดํŠธ๋ณด๋‹ค ๋จผ์ € fetch๊ฐ€ ์ด๋ฃจ์–ด์กŒ๋˜ ์ ์ž…๋‹ˆ๋‹ค. useQuery์˜ enabled ์˜ต์…˜์„ ํ†ตํ•˜์—ฌ ๊ธ‰ํ•œ ๋ถˆ์€ ๊ป์ง€๋งŒ, ์ „์—ญ ์ƒํƒœ ๊ด€๋ฆฌ ๋กœ์ง์ด ๋ถˆ์™„์ „ํ•œ ๊ฒƒ ๊ฐ™๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์–ด zustand์™€ useQuery์˜ ๋™์ž‘ ๋ฐฐ๊ฒฝ์„ ๋” ๊ณต๋ถ€ํ•œ ํ›„ ์ฝ”๋“œ๋ฅผ ๊ฐœ์„ ํ•ด๋ณผ ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.

 

 

(5) ๊ทธ์™ธ ๊ธฐ๋Šฅ๋“ค

Swiper.js, React Spring Bottom Sheet, ReactBigCalendar, ToastUIEditor ๋“ฑ ๋‹ค์–‘ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋น ๋ฅด๊ฒŒ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

ํŠนํžˆ ReactBigCalendar์„ ์ปค์Šคํ„ฐ๋งˆ์ด์ฆˆํ•˜๋ฉฐ ํด๋ž˜์Šคํ˜• ์ปดํฌ๋„ŒํŠธ๋กœ ์ž‘์„ฑ๋œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ์ฝ”๋“œ๋ฅผ ๋œฏ์–ด๋ดค๋˜ ๊ฒƒ์ด ํž˜๋“ค์—ˆ๋˜ ๋งŒํผ ๋งŽ์ด ๊ธฐ์–ต์— ๋‚จ์Šต๋‹ˆ๋‹ค.

 

 

 

ํ”„๋กœ์ ํŠธ ๋””๋ฒจ๋กญ

๋ชฉํ‘œํ–ˆ๋˜ ๊ธฐ๋Šฅ๋“ค์„ ์™„์„ฑํ•˜๋ฉฐ ์บก์Šคํ†ค์„ ์„ฑ๊ณต์ ์œผ๋กœ ๋งˆ๋ฌด๋ฆฌํ•˜์˜€์Šต๋‹ˆ๋‹ค!

 

๊ทธ๋Ÿฌ๋‚˜ ์˜ฌํ•ด ์บก์Šคํ†ค๊นŒ์ง€ 4๊ฐœ์˜ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งˆ์นœ ํ›„, ๋ฉฐ์น  ๋™์•ˆ ์ œ๊ฐ€ ์™œ ์—ฌ๋Ÿฌ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งˆ์ณค์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ๊ธฐ๋Œ€ํ•œ ๋งŒํผ์˜ ์„ฑ์žฅ์„ ์ด๋ฃจ์ง€ ๋ชป ํ–ˆ๋‚˜์— ๋Œ€ํ•œ ๊ณ ๋ฏผ์„ ํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ์ž๋Š” ๊ตฌํ˜„๋งŒ ํ•˜๋Š” ์‚ฌ๋žŒ์ด ์•„๋‹ˆ๋ผ๋Š” ๊ฒƒ์„ ์ž˜ ์•Œ๋ฉด์„œ, ๊ทธ์ € ๊ฒฐ๊ณผ๋ฌผ ๋‚ด๋†“๊ธฐ์— ๊ธ‰๊ธ‰ํ–ˆ๋˜ ๊ฒƒ ๋•Œ๋ฌธ์ด์ง€ ์•Š์„๊นŒ ์‹ถ๋„ค์š”.

๊ทธ๋ž˜์„œ ์ง€๋‚œ ๋‚ ์„ ๋ฐ˜์„ฑํ•˜๋ฉฐ ๋‹จํ†ต ํ”„๋กœ์ ํŠธ ๋””๋ฒจ๋กญ์€ ๊นŠ์ด ์žˆ๋Š” ํ•™์Šต์— ์ดˆ์ ์„ ๋‘๋ ค ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ฝ”๋“œ ํ’ˆ์งˆ๊ณผ ๋ฆฌ์•กํŠธ ๋ Œ๋”๋ง ์ตœ์ ํ™”๋ฅผ ์ค‘์‹ฌ์œผ๋กœ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค!

 

 

 

๊ทธ๋Ÿผ ์ด๋ฒˆ ํšŒ๊ณ ๊ธ€์—์„œ ์ ์—ˆ๋˜ ๊ฐœ์„ ์‚ฌํ•ญ์„ ๊ณ ์ณ ๋‹ค์Œ ํšŒ๊ณ ๊ธ€๋กœ ๊ธˆ๋ฐฉ ๋Œ์•„์˜ค๊ฒ ์Šต๋‹ˆ๋‹ค.

 ๊ธด ๊ธ€ ์ฝ์–ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!