Skip to content

๐Ÿชต 7. React ํ™˜๊ฒฝ์—์„œ์˜ ์ƒˆ๋กœ๊ณ ์นจ ๋ฐฉ์ง€์™€ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๊ตฌํ˜„: ๊ฒŒ์ž„ ์ดํƒˆ ๋ฐฉ์ง€ ์ „๋žต

Taeyeon Yoon edited this page Dec 5, 2024 · 1 revision

๊ฒŒ์ž„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‚ฌ์šฉ์ž์˜ ์˜๋„์น˜ ์•Š์€ ํŽ˜์ด์ง€ ์ดํƒˆ์„ ๋ฐฉ์ง€ํ•˜๊ณ , ์ƒˆ๋กœ๊ณ ์นจ ์‹œ ์ ์ ˆํ•œ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋ฅผ ๊ตฌํ˜„ํ•œ ๊ฒฝํ—˜์„ ๊ณต์œ ํ•˜๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

1. ๊ตฌํ˜„ ๋ฐฐ๊ฒฝ๊ณผ ์š”๊ตฌ์‚ฌํ•ญ

๊ฒŒ์ž„ ์ง„ํ–‰ ์ค‘ ์‚ฌ์šฉ์ž๊ฐ€ ์‹ค์ˆ˜๋กœ ๋ธŒ๋ผ์šฐ์ € ๋’ค๋กœ ๊ฐ€๊ธฐ๋ฅผ ๋ˆ„๋ฅด๊ฑฐ๋‚˜ ์ƒˆ๋กœ๊ณ ์นจ์„ ํ•˜๋Š” ๊ฒฝ์šฐ, ๊ฒŒ์ž„ ์ƒํƒœ๊ฐ€ ์†์‹ค๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” UX๋ฅผ ์ €ํ•ดํ•˜๋Š” ์ค‘์š”ํ•œ ์š”์†Œ์˜€์Šต๋‹ˆ๋‹ค.

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

ํ•ด๊ฒฐํ•ด์•ผ ํ•  ๋ฌธ์ œ๋“ค

  • ๋ธŒ๋ผ์šฐ์ € ํžˆ์Šคํ† ๋ฆฌ ์กฐ์ž‘ ๋ฐฉ์ง€
    • ๋’ค๋กœ ๊ฐ€๊ธฐ/์•ž์œผ๋กœ ๊ฐ€๊ธฐ ์‹œ๋„ ์‹œ ์‚ฌ์šฉ์ž ํ™•์ธ
    • ํ˜„์žฌ URL ์œ ์ง€๋ฅผ ์œ„ํ•œ history stack ๊ด€๋ฆฌ
    • popstate ์ด๋ฒคํŠธ ํ•ธ๋“ค๋ง
  • ์ƒˆ๋กœ๊ณ ์นจ ์‹œ๋‚˜๋ฆฌ์˜ค ์ฒ˜๋ฆฌ
    • ์ƒˆ๋กœ๊ณ ์นจ ์‹œ๋„ ์‹œ ์‚ฌ์šฉ์ž ํ™•์ธ
    • Storage๋ฅผ ํ™œ์šฉํ•œ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ํ”Œ๋ž˜๊ทธ ๊ด€๋ฆฌ
    • ๋ฉ”์ธ ํŽ˜์ด์ง€๋กœ์˜ ์ž๋™ ๋ฆฌ๋‹ค์ด๋ ‰์…˜
  • ๊ฒŒ์ž„ ์ดํƒˆ ๋ฐฉ์ง€ UX
    • ๋ชจ๋‹ฌ์„ ํ†ตํ•œ ๋ช…ํ™•ํ•œ ์‚ฌ์šฉ์ž ์˜์‚ฌ ํ™•์ธ
    • ์‹ค์ˆ˜๋กœ ์ธํ•œ ๊ฒŒ์ž„ ์ข…๋ฃŒ ๋ฐฉ์ง€

2. ๊ตฌํ˜„ ๊ณผ์ •์—์„œ์˜ ์‹œํ–‰์ฐฉ์˜ค

2.1. React Router์˜ ๋นŒํŠธ์ธ ์†”๋ฃจ์…˜ ์‹œ๋„

์ฒ˜์Œ์—๋Š” React Router์—์„œ ์ œ๊ณตํ•˜๋Š” useBeforeUnload์™€ ๊ฐ™์€ ๋‚ด์žฅ ํ›…์„ ์‚ฌ์šฉํ•ด ๊ตฌํ˜„์„ ์‹œ๋„ํ–ˆ์Šต๋‹ˆ๋‹ค.

// โŒ React Router ํ›… ์‚ฌ์šฉ์˜ ํ•œ๊ณ„
useBeforeUnload({
  when: true,
  message: "๊ฒŒ์ž„์„ ์ข…๋ฃŒํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?"
});

ํ•˜์ง€๋งŒ ์ด ๋ฐฉ์‹์€ ๋ช‡ ๊ฐ€์ง€ ๋ฌธ์ œ์ ์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

  • ๋ธŒ๋ผ์šฐ์ €๋ณ„๋กœ ๋™์ž‘์ด ์ผ๊ด€๋˜์ง€ ์•Š์Œ
  • ์ปค์Šคํ…€ ๋ชจ๋‹ฌ ๋Œ€์‹  ๋ธŒ๋ผ์šฐ์ € ๊ธฐ๋ณธ ํŒ์—…๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
  • history stack ์กฐ์ž‘์ด ์ œํ•œ์ 

๊ฐ€์žฅ ํฐ ๋ฌธ์ œ๋Š” ๋’ค๋กœ๊ฐ€๊ธฐ ๋ฐ ์ƒˆ๋กœ๊ณ ์นจ ์‹œ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๋˜๋Š” ๋กœ์ง์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์—†์–ด ํ•ต์‹ฌ์ ์ธ ๋ฌธ์ œ์˜€์Šต๋‹ˆ๋‹ค.

2.2. ์ปค์Šคํ…€ ํ›… ์ ‘๊ทผ ๋ฐฉ์‹

๋‹ค์Œ์œผ๋กœ ์ง์ ‘ ์ปค์Šคํ…€ ํ›…์„ ๋งŒ๋“ค์–ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค ํ–ˆ์Šต๋‹ˆ๋‹ค.

// โŒ ์ปค์Šคํ…€ ํ›… ๋ฐฉ์‹์˜ ํ•œ๊ณ„
const useNavigationGuard = () => {
  useEffect(() => {
    // ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ๋“ฑ๋ก
    // ์ƒํƒœ ๊ด€๋ฆฌ ๋กœ์ง
  }, []);
};

๋” ํ•จ์ˆ˜์ ์ธ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ด์ง€๊ณ , ๋กœ์ง ์žฌ์‚ฌ์šฉ์ด ์ง๊ด€์ ์œผ๋กœ ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์ด ๋ฐฉ์‹์˜ ๋ฌธ์ œ์ ์€ ์•„๋ž˜์™€ ๊ฐ™์•˜์Šต๋‹ˆ๋‹ค.

  • ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉ์‹œ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์ค‘๋ณต ๋“ฑ๋ก ๊ฐ€๋Šฅ์„ฑ
  • ์ „์—ญ์ ์ธ ๋„ค๋น„๊ฒŒ์ด์…˜ ์ œ์–ด๊ฐ€ ์–ด๋ ค์›€
  • ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ์˜ ํŠน์ • ์œ„์น˜์—์„œ๋งŒ ๋™์ž‘ํ•˜๋„๋ก ์ œํ•œํ•˜๊ธฐ ์–ด๋ ค์›€

2.3. ์ปดํฌ๋„ŒํŠธ ๊ธฐ๋ฐ˜ ํ•ด๊ฒฐ์ฑ… ์ฑ„ํƒ

์ตœ์ข…์ ์œผ๋กœ ๋…๋ฆฝ๋œ ์ปดํฌ๋„ŒํŠธ๋กœ ๊ตฌํ˜„ํ•˜์—ฌ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค.

const BrowserNavigationGuard = () => {
  const navigate = useNavigate();
  const location = useLocation();
  const modalActions = useNavigationModalStore((state) => state.actions);

  useEffect(() => {
    // ์ƒˆ๋กœ๊ณ ์นจ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ํ•ธ๋“ค๋Ÿฌ
    const handleBeforeUnload = (e: BeforeUnloadEvent) => {
      e.preventDefault();
      e.returnValue = '';

      // ์ƒˆ๋กœ๊ณ ์นจ ์‹œ ๋ฉ”์ธ์œผ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธํ•˜๊ธฐ ์œ„ํ•œ ํ”Œ๋ž˜๊ทธ ์ €์žฅ
      sessionStorage.setItem('shouldRedirect', 'true');

      return '๊ฒŒ์ž„์„ ์ข…๋ฃŒํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?';
    };

    // ๋ธŒ๋ผ์šฐ์ € navigation ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ํ•ธ๋“ค๋Ÿฌ
    const handlePopState = (e: PopStateEvent) => {
      e.preventDefault();
      modalActions.openModal();

      // ํ˜„์žฌ URL ์œ ์ง€๋ฅผ ์œ„ํ•œ history stack ์กฐ์ž‘
      window.history.pushState(null, '', location.pathname);
    };

    window.history.pushState(null, '', location.pathname);
    window.addEventListener('beforeunload', handleBeforeUnload);
    window.addEventListener('popstate', handlePopState);

    // ์ƒˆ๋กœ๊ณ ์นจ ํ›„ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ์ฒ˜๋ฆฌ
    const shouldRedirect = sessionStorage.getItem('shouldRedirect');
    if (shouldRedirect === 'true' && location.pathname !== '/') {
      navigate('/', { replace: true });
      sessionStorage.removeItem('shouldRedirect');
    }

    return () => {
      window.removeEventListener('beforeunload', handleBeforeUnload);
      window.removeEventListener('popstate', handlePopState);
    };
  }, [navigate, location.pathname]);

  return null;
};
  • ํŠน์ • ๋ผ์šฐํŠธ(๊ฒŒ์ž„ ์ง„ํ–‰ ์ค‘)์—์„œ๋งŒ ๋™์ž‘ํ•ด์•ผ ํ•˜๋Š” ๋ช…ํ™•ํ•œ ๋ฒ”์œ„ ์กด์žฌ
  • React์˜ ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ํ™œ์šฉํ•œ history stack์„ ๋ช…ํ™•ํ•œ ์ œ์–ด ๊ฐ€๋Šฅ
  • ์ปค์Šคํ…€ UI์™€์˜ ์ž์—ฐ์Šค๋Ÿฌ์šด ํ†ตํ•ฉ
  • ์žฌ์‚ฌ์šฉ์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ ํ–ฅ์ƒ

3. ๊ตฌํ˜„ ์ „๋žต ์„ค๋ช…

3.1. History Stack ๊ด€๋ฆฌ

window.history.pushState(null, '', location.pathname);

์ดˆ๊ธฐ ์ง„์ž… ์‹œ ํ˜„์žฌ ์ƒํƒœ๋ฅผ history stack์— ์ถ”๊ฐ€ํ•ด navigation ์ด๋ฒคํŠธ๋ฅผ ๊ฐ์ง€ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋ฐ˜์„ ๋งˆ๋ จํ–ˆ์Šต๋‹ˆ๋‹ค.

3.2. ์ƒˆ๋กœ๊ณ ์นจ ์ฒ˜๋ฆฌ ์ „๋žต

Storage API ์„ ํƒ์— ๋Œ€ํ•œ ๊ณ ์ฐฐ

์ƒˆ๋กœ๊ณ ์นจ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ํ”Œ๋ž˜๊ทธ ์ €์žฅ ๋ฐฉ์‹์„ ๊ฒฐ์ •ํ•  ๋•Œ, localStorage์™€ sessionStorage ๋‘ ๊ฐ€์ง€ ์˜ต์…˜์„ ๊ฒ€ํ† ํ–ˆ์Šต๋‹ˆ๋‹ค.

LocalStorage๋ฅผ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ์˜ ๋ฌธ์ œ์ 

์ฒ˜์Œ์—๋Š” LocalStorage๋ฅผ ์‚ฌ์šฉํ–ˆ์ง€๋งŒ, ์•„๋ž˜์™€ ๊ฐ™์€ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.

// โŒ localStorage ์‚ฌ์šฉ ์‹œ์˜ ๋ฌธ์ œ
localStorage.setItem('shouldRedirect', 'true');
  • ๋ธŒ๋ผ์šฐ์ €/ํƒญ์ด ๋‹ซํ˜€๋„ ํ”Œ๋ž˜๊ทธ๊ฐ€ ๊ณ„์† ์œ ์ง€๋จ
  • ๋‹ค์Œ ์„ธ์…˜์—์„œ ์˜๋„์น˜ ์•Š์€ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๋ฐœ์ƒ ๊ฐ€๋Šฅ
  • ํ”Œ๋ž˜๊ทธ ์‚ญ์ œ ๋กœ์ง์ด ๋ช…์‹œ์ ์œผ๋กœ ํ•„์š”

SessionStorage ์ฑ„ํƒ ์ด์œ 

// โœ… sessionStorage ์‚ฌ์šฉ์˜ ์žฅ์ 
sessionStorage.setItem('shouldRedirect', 'true');
  1. ์ž๋™ ์ •๋ฆฌ (Auto Cleanup)
    • ๋ธŒ๋ผ์šฐ์ €/ํƒญ์ด ๋‹ซํž ๋•Œ ๋ฐ์ดํ„ฐ๊ฐ€ ์ž๋™์œผ๋กœ ์‚ญ์ œ๋จ
    • ๋‹ค์Œ ์„ธ์…˜์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์Œ
  2. ์„ธ์…˜ ๋ฒ”์œ„ ์ œํ•œ
    • ๊ฐ™์€ ํƒญ ๋‚ด์—์„œ๋งŒ ๋ฐ์ดํ„ฐ ์œ ์ง€
    • ๋‹ค๋ฅธ ํƒญ์˜ ๊ฒŒ์ž„ ์„ธ์…˜๊ณผ ์ถฉ๋Œ ๋ฐฉ์ง€
  3. ๋ช…์‹œ์ ์ธ ์ˆ˜๋ช… ์ฃผ๊ธฐ
    • ๊ฒŒ์ž„ ์„ธ์…˜๊ณผ ๋ฐ์ดํ„ฐ์˜ ์ˆ˜๋ช… ์ฃผ๊ธฐ๊ฐ€ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ผ์น˜
    • ์ถ”๊ฐ€์ ์ธ cleanup ๋กœ์ง์ด ๋ถˆํ•„์š”

์ด๋Ÿฌํ•œ ์ด์œ ๋กœ sessionStorage๊ฐ€ ์ƒˆ๋กœ๊ณ ์นจ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ํ”Œ๋ž˜๊ทธ ๊ด€๋ฆฌ์— ๋” ์ ํ•ฉํ•œ ์„ ํƒ์ด์—ˆ์Šต๋‹ˆ๋‹ค.

3.3. Navigation ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ

const handlePopState = (e: PopStateEvent) => {
  e.preventDefault();
  modalActions.openModal();
  window.history.pushState(null, '', location.pathname);
};

๋’ค๋กœ ๊ฐ€๊ธฐ/์•ž์œผ๋กœ ๊ฐ€๊ธฐ ์‹œ๋„ ์‹œ ์ด๋ฅผ ์ค‘๋‹จํ•˜๊ณ  ์‚ฌ์šฉ์ž ํ™•์ธ ๋ชจ๋‹ฌ์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.

3.4. ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ์ฒ˜๋ฆฌ

if (shouldRedirect === 'true' && location.pathname !== '/') {
  navigate('/', { replace: true });
  sessionStorage.removeItem('shouldRedirect');
}

์ƒˆ๋กœ๊ณ ์นจ ํ›„ ์„ธ์…˜์Šคํ† ๋ฆฌ์ง€์— shouldRedirect๊ฐ€ ์žˆ๋‹ค๋ฉด ๋ฉ”์ธ ํŽ˜์ด์ง€๋กœ์˜ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

4. NavigationModal ์—ฐ๋™

export const NavigationModal = () => {
  const navigate = useNavigate();
  const isOpen = useNavigationModalStore((state) => state.isOpen);
  const actions = useNavigationModalStore((state) => state.actions);

  const handleConfirmExit = () => {
    actions.closeModal();
    navigate('/', { replace: true });
  };

  return (
    <Modal
      title="๊ฒŒ์ž„ ๋‚˜๊ฐ€๊ธฐ"
      isModalOpened={isOpen}
      closeModal={actions.closeModal}
    >
      <p>์ •๋ง ๊ฒŒ์ž„์„ ๋‚˜๊ฐ€์‹ค๊ฑฐ์—์š”...??</p>
      <Button onClick={handleConfirmExit}>๋‚˜๊ฐˆ๋ž˜์š”..</Button>
      <Button onClick={actions.closeModal}>์•ˆ๋‚˜๊ฐˆ๋ž˜์š”!</Button>
    </Modal>
  );
};

5. ๊ฒฐ๊ณผ ๋ฐ ์ด์ 

์ด๋Ÿฌํ•œ ๊ตฌํ˜„์„ ํ†ตํ•ด ์•„๋ž˜์™€ ๊ฐ™์€ ์ด์ ์„ ์–ป์„ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

  1. ์˜๋„์น˜ ์•Š์€ ๊ฒŒ์ž„ ์ดํƒˆ ๋ฐฉ์ง€
  2. ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ช…ํ™•ํ•œ ํ”ผ๋“œ๋ฐฑ ์ œ๊ณต
  3. ๊ฒŒ์ž„ ์ƒํƒœ ๋ณด์กด์„ ์œ„ํ•œ ์ ์ ˆํ•œ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ์ฒ˜๋ฆฌ
  4. React Router์™€์˜ ์ž์—ฐ์Šค๋Ÿฌ์šด ํ†ตํ•ฉ

์ด ๊ตฌํ˜„์€ ๊ฒŒ์ž„์˜ ์•ˆ์ •์„ฑ์„ ๋†’์ด๊ณ  ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ๊ฐœ์„ ํ•˜๋Š” ๋ฐ ํฐ ๋„์›€์ด ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ˜Ž ์›จ๋ฒ ๋ฒ ๋ฒ ๋ฒฑ

๐Ÿ‘ฎ๐Ÿป ํŒ€ ๊ทœ์น™

๐Ÿ’ป ํ”„๋กœ์ ํŠธ

๐Ÿชต ์›จ๋ฒ ๋ฒฑ ๊ธฐ์ˆ ๋กœ๊ทธ

๐Ÿช„ ๋ฐ๋ชจ ๊ณต์œ 

๐Ÿ”„ ์Šคํ”„๋ฆฐํŠธ ๊ธฐ๋ก

๐Ÿ“— ํšŒ์˜๋ก

Clone this wiki locally