-
Notifications
You must be signed in to change notification settings - Fork 7
๐ชต 7. React ํ๊ฒฝ์์์ ์๋ก๊ณ ์นจ ๋ฐฉ์ง์ ๋ฆฌ๋ค์ด๋ ํธ ๊ตฌํ: ๊ฒ์ ์ดํ ๋ฐฉ์ง ์ ๋ต
๊ฒ์ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ฌ์ฉ์์ ์๋์น ์์ ํ์ด์ง ์ดํ์ ๋ฐฉ์งํ๊ณ , ์๋ก๊ณ ์นจ ์ ์ ์ ํ ๋ฆฌ๋ค์ด๋ ํธ๋ฅผ ๊ตฌํํ ๊ฒฝํ์ ๊ณต์ ํ๊ณ ์ ํฉ๋๋ค.
๊ฒ์ ์งํ ์ค ์ฌ์ฉ์๊ฐ ์ค์๋ก ๋ธ๋ผ์ฐ์ ๋ค๋ก ๊ฐ๊ธฐ๋ฅผ ๋๋ฅด๊ฑฐ๋ ์๋ก๊ณ ์นจ์ ํ๋ ๊ฒฝ์ฐ, ๊ฒ์ ์ํ๊ฐ ์์ค๋๋ ๋ฌธ์ ๊ฐ ์์์ต๋๋ค. ์ด๋ UX๋ฅผ ์ ํดํ๋ ์ค์ํ ์์์์ต๋๋ค.
๋ํ, ์๋ฒ์์๋ reconnected
์ ๋๋ก์ ๋ฐ ์ฑํ
๋ก๊ทธ๋ฅผ ์ ์งํ๊ธฐ ์ด๋ ค์ด ์ํฉ์ด์์ต๋๋ค. ๊ทธ๋์ ํ์คํ ๋ฆฌ ๋ฐ ์๋ก๊ณ ์นจ ์กฐ์ ์ ๋ฆฌ๋ค์ด๋ ํธ ํด์ผํ๋ ์๊ตฌ์ฌํญ์ด ์๊ฒผ์ต๋๋ค.
- ๋ธ๋ผ์ฐ์ ํ์คํ ๋ฆฌ ์กฐ์ ๋ฐฉ์ง
- ๋ค๋ก ๊ฐ๊ธฐ/์์ผ๋ก ๊ฐ๊ธฐ ์๋ ์ ์ฌ์ฉ์ ํ์ธ
- ํ์ฌ URL ์ ์ง๋ฅผ ์ํ history stack ๊ด๋ฆฌ
- popstate ์ด๋ฒคํธ ํธ๋ค๋ง
- ์๋ก๊ณ ์นจ ์๋๋ฆฌ์ค ์ฒ๋ฆฌ
- ์๋ก๊ณ ์นจ ์๋ ์ ์ฌ์ฉ์ ํ์ธ
- Storage๋ฅผ ํ์ฉํ ๋ฆฌ๋ค์ด๋ ํธ ํ๋๊ทธ ๊ด๋ฆฌ
- ๋ฉ์ธ ํ์ด์ง๋ก์ ์๋ ๋ฆฌ๋ค์ด๋ ์
- ๊ฒ์ ์ดํ ๋ฐฉ์ง UX
- ๋ชจ๋ฌ์ ํตํ ๋ช ํํ ์ฌ์ฉ์ ์์ฌ ํ์ธ
- ์ค์๋ก ์ธํ ๊ฒ์ ์ข ๋ฃ ๋ฐฉ์ง
์ฒ์์๋ React Router์์ ์ ๊ณตํ๋ useBeforeUnload
์ ๊ฐ์ ๋ด์ฅ ํ
์ ์ฌ์ฉํด ๊ตฌํ์ ์๋ํ์ต๋๋ค.
// โ React Router ํ
์ฌ์ฉ์ ํ๊ณ
useBeforeUnload({
when: true,
message: "๊ฒ์์ ์ข
๋ฃํ์๊ฒ ์ต๋๊น?"
});
ํ์ง๋ง ์ด ๋ฐฉ์์ ๋ช ๊ฐ์ง ๋ฌธ์ ์ ์ด ์์์ต๋๋ค.
- ๋ธ๋ผ์ฐ์ ๋ณ๋ก ๋์์ด ์ผ๊ด๋์ง ์์
- ์ปค์คํ ๋ชจ๋ฌ ๋์ ๋ธ๋ผ์ฐ์ ๊ธฐ๋ณธ ํ์ ๋ง ์ฌ์ฉ ๊ฐ๋ฅ
- history stack ์กฐ์์ด ์ ํ์
๊ฐ์ฅ ํฐ ๋ฌธ์ ๋ ๋ค๋ก๊ฐ๊ธฐ ๋ฐ ์๋ก๊ณ ์นจ ์ ๋ฆฌ๋ค์ด๋ ํธ ๋๋ ๋ก์ง์ ์ถ๊ฐํ ์ ์์ด ํต์ฌ์ ์ธ ๋ฌธ์ ์์ต๋๋ค.
๋ค์์ผ๋ก ์ง์ ์ปค์คํ ํ ์ ๋ง๋ค์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ค ํ์ต๋๋ค.
// โ ์ปค์คํ
ํ
๋ฐฉ์์ ํ๊ณ
const useNavigationGuard = () => {
useEffect(() => {
// ์ด๋ฒคํธ ํธ๋ค๋ฌ ๋ฑ๋ก
// ์ํ ๊ด๋ฆฌ ๋ก์ง
}, []);
};
๋ ํจ์์ ์ธ ์ ๊ทผ ๊ฐ๋ฅํด์ง๊ณ , ๋ก์ง ์ฌ์ฌ์ฉ์ด ์ง๊ด์ ์ผ๋ก ํ ์ ์์์ต๋๋ค.
ํ์ง๋ง ์ด ๋ฐฉ์์ ๋ฌธ์ ์ ์ ์๋์ ๊ฐ์์ต๋๋ค.
- ์ฌ๋ฌ ์ปดํฌ๋ํธ์์ ์ฌ์ฉ์ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ์ค๋ณต ๋ฑ๋ก ๊ฐ๋ฅ์ฑ
- ์ ์ญ์ ์ธ ๋ค๋น๊ฒ์ด์ ์ ์ด๊ฐ ์ด๋ ค์
- ์ปดํฌ๋ํธ ํธ๋ฆฌ์ ํน์ ์์น์์๋ง ๋์ํ๋๋ก ์ ํํ๊ธฐ ์ด๋ ค์
์ต์ข ์ ์ผ๋ก ๋ ๋ฆฝ๋ ์ปดํฌ๋ํธ๋ก ๊ตฌํํ์ฌ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์ต๋๋ค.
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์์ ์์ฐ์ค๋ฌ์ด ํตํฉ
- ์ฌ์ฌ์ฉ์ฑ๊ณผ ์ ์ง๋ณด์์ฑ ํฅ์
window.history.pushState(null, '', location.pathname);
์ด๊ธฐ ์ง์
์ ํ์ฌ ์ํ๋ฅผ history stack์ ์ถ๊ฐํด navigation
์ด๋ฒคํธ๋ฅผ ๊ฐ์งํ ์ ์๋ ๊ธฐ๋ฐ์ ๋ง๋ จํ์ต๋๋ค.
์๋ก๊ณ ์นจ ์ฒ๋ฆฌ๋ฅผ ์ํ ํ๋๊ทธ ์ ์ฅ ๋ฐฉ์์ ๊ฒฐ์ ํ ๋, localStorage
์ sessionStorage
๋ ๊ฐ์ง ์ต์
์ ๊ฒํ ํ์ต๋๋ค.
์ฒ์์๋ LocalStorage
๋ฅผ ์ฌ์ฉํ์ง๋ง, ์๋์ ๊ฐ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค.
// โ localStorage ์ฌ์ฉ ์์ ๋ฌธ์
localStorage.setItem('shouldRedirect', 'true');
- ๋ธ๋ผ์ฐ์ /ํญ์ด ๋ซํ๋ ํ๋๊ทธ๊ฐ ๊ณ์ ์ ์ง๋จ
- ๋ค์ ์ธ์ ์์ ์๋์น ์์ ๋ฆฌ๋ค์ด๋ ํธ ๋ฐ์ ๊ฐ๋ฅ
- ํ๋๊ทธ ์ญ์ ๋ก์ง์ด ๋ช ์์ ์ผ๋ก ํ์
// โ
sessionStorage ์ฌ์ฉ์ ์ฅ์
sessionStorage.setItem('shouldRedirect', 'true');
-
์๋ ์ ๋ฆฌ (Auto Cleanup)
- ๋ธ๋ผ์ฐ์ /ํญ์ด ๋ซํ ๋ ๋ฐ์ดํฐ๊ฐ ์๋์ผ๋ก ์ญ์ ๋จ
- ๋ค์ ์ธ์ ์ ์ํฅ์ ์ฃผ์ง ์์
-
์ธ์
๋ฒ์ ์ ํ
- ๊ฐ์ ํญ ๋ด์์๋ง ๋ฐ์ดํฐ ์ ์ง
- ๋ค๋ฅธ ํญ์ ๊ฒ์ ์ธ์ ๊ณผ ์ถฉ๋ ๋ฐฉ์ง
-
๋ช
์์ ์ธ ์๋ช
์ฃผ๊ธฐ
- ๊ฒ์ ์ธ์ ๊ณผ ๋ฐ์ดํฐ์ ์๋ช ์ฃผ๊ธฐ๊ฐ ์์ฐ์ค๋ฝ๊ฒ ์ผ์น
- ์ถ๊ฐ์ ์ธ cleanup ๋ก์ง์ด ๋ถํ์
์ด๋ฌํ ์ด์ ๋ก sessionStorage
๊ฐ ์๋ก๊ณ ์นจ ์ฒ๋ฆฌ๋ฅผ ์ํ ํ๋๊ทธ ๊ด๋ฆฌ์ ๋ ์ ํฉํ ์ ํ์ด์์ต๋๋ค.
const handlePopState = (e: PopStateEvent) => {
e.preventDefault();
modalActions.openModal();
window.history.pushState(null, '', location.pathname);
};
๋ค๋ก ๊ฐ๊ธฐ/์์ผ๋ก ๊ฐ๊ธฐ ์๋ ์ ์ด๋ฅผ ์ค๋จํ๊ณ ์ฌ์ฉ์ ํ์ธ ๋ชจ๋ฌ์ ํ์ํฉ๋๋ค.
if (shouldRedirect === 'true' && location.pathname !== '/') {
navigate('/', { replace: true });
sessionStorage.removeItem('shouldRedirect');
}
์๋ก๊ณ ์นจ ํ ์ธ์
์คํ ๋ฆฌ์ง์ shouldRedirect
๊ฐ ์๋ค๋ฉด ๋ฉ์ธ ํ์ด์ง๋ก์ ๋ฆฌ๋ค์ด๋ ํธ๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค.
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>
);
};
์ด๋ฌํ ๊ตฌํ์ ํตํด ์๋์ ๊ฐ์ ์ด์ ์ ์ป์ ์ ์์์ต๋๋ค.
- ์๋์น ์์ ๊ฒ์ ์ดํ ๋ฐฉ์ง
- ์ฌ์ฉ์์๊ฒ ๋ช ํํ ํผ๋๋ฐฑ ์ ๊ณต
- ๊ฒ์ ์ํ ๋ณด์กด์ ์ํ ์ ์ ํ ๋ฆฌ๋ค์ด๋ ํธ ์ฒ๋ฆฌ
- React Router์์ ์์ฐ์ค๋ฌ์ด ํตํฉ
์ด ๊ตฌํ์ ๊ฒ์์ ์์ ์ฑ์ ๋์ด๊ณ ์ฌ์ฉ์ ๊ฒฝํ์ ๊ฐ์ ํ๋ ๋ฐ ํฐ ๋์์ด ๋์์ต๋๋ค.
- 1. ๊ฐ๋ฐ ํ๊ฒฝ ์ธํ ๋ฐ ํ๋ก์ ํธ ๋ฌธ์ํ
- 2. ์ค์๊ฐ ํต์
- 3. ์ธํ๋ผ ๋ฐ CI/CD
- 4. ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ด Canvas ๊ตฌํํ๊ธฐ
- 5. ์บ๋ฒ์ค ๋๊ธฐํ๋ฅผ ์ํ ์์ CRDT ๊ตฌํ๊ธฐ
-
6. ์ปดํฌ๋ํธ ํจํด๋ถํฐ ์น์์ผ๊น์ง, ํจ์จ์ ์ธ FE ์ค๊ณ
- ์ข์ ์ปดํฌ๋ํธ๋ ๋ฌด์์ธ๊ฐ? + Headless Pattern
- ํจ์จ์ ์ธ UI ์ปดํฌ๋ํธ ์คํ์ผ๋ง: Tailwind CSS + cn.ts
- Tailwind CSS๋ก ๋์์ธ ์์คํ ๋ฐ UI ์ปดํฌ๋ํธ ์ธํ
- ์น์์ผ ํด๋ผ์ด์ธํธ ๊ตฌํ๊ธฐ: React ํ๊ฒฝ์์ ํจ์จ์ ์ธ ์น์์ผ ์ํคํ ์ฒ
- ์น์์ผ ํด๋ผ์ด์ธํธ ์ฝ๋ ๋ถ์ ๋ฐ ๊ณต์
- 7. ํธ๋ฌ๋ธ ์ํ ๋ฐ ์ฑ๋ฅ/UX ๊ฐ์
- 1์ฃผ์ฐจ ๊ธฐ์ ๊ณต์
- 2์ฃผ์ฐจ ๋ฐ๋ชจ ๋ฐ์ด
- 3์ฃผ์ฐจ ๋ฐ๋ชจ ๋ฐ์ด
- 4์ฃผ์ฐจ ๋ฐ๋ชจ ๋ฐ์ด
- 5์ฃผ์ฐจ ๋ฐ๋ชจ ๋ฐ์ด
- WEEK 06 ์ฃผ๊ฐ ๊ณํ
- WEEK 06 ๋ฐ์ผ๋ฆฌ ์คํฌ๋ผ
- WEEK 06 ์ฃผ๊ฐ ํ๊ณ