이전 글) [원티드 프리온보딩 챌린지] React에서 History API 사용하여 SPA Router 기능 구현하기
App.js
안에 모든 코드를 작성했는데, 이를 Router
, Route
컴포넌트, useRouter()
훅으로 분리하려고 한다.
📁 src
⎿ 📄 App.js
⎿ 📄 index.js
⎿ 📁 component
⎿ 📄 Root.js
⎿ 📄 About.js
⎿ 📄 Router.js
⎿ 📄 Route.js
⎿ 📁 hooks
⎿ 📄 useRouter.js
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(
<Router>
<Route path="/" component={<Root />} />
<Route path="/about" component={<About />} />
</Router>
);
function Router({ children }) {
return children;
}
export default Router;
function Route({ path, component }) {
const { currentPath } = useRouter();
return currentPath === path ? component : null;
// pathname이 /이라면 <Home /> 컴포넌트를 렌더링한다.
// pathname이 /about이라면 <About /> 컴포넌트를 렌더링한다.
}
export default Route;
import { useState, useEffect } from "react";
export const useRouter = () => {
const [currentPath, setCurrentPath] = useState(window.location.pathname);
useEffect(() => {
const handlePopState = () => {
setCurrentPath(window.location.pathname);
}
window.addEventListener("popstate", handlePopState);
return () => {
window.removeEventListener("popstate", handlePopState);
};
}, []);
const updateURL = (path) => {
window.history.pushState(null, "", path);
setCurrentPath(window.location.pathname);
};
return { updateURL, currentPath };
};
import { useRouter } from "../hooks/useRouter";
const About = () => {
const { updateURL } = useRouter();
return (
<div>
<h1>About</h1>
<button onClick={() => updateURL("/")}>Home으로 가기</button>
</div>
);
};
export default About;
이렇게 useRouter
훅으로 분리하고 나니
"About으로 가기" 버튼을 눌렀을 때 updateURL()
함수가 실행되고, currentPath
상태가 '/about'
으로 업데이트되지만,
<Root>
컴포넌트는 currentPath
상태를 사용하고 있지 않기 때문에 리렌더링을 수행하지 않는다.
useRouter
훅의 코드를 다시 살펴보자.
export const useRouter = () => {
const [currentPath, setCurrentPath] = useState(window.location.pathname);
useEffect(() => {
const handlePopState = () => {
setCurrentPath(window.location.pathname);
}
window.addEventListener("popstate", handlePopState);
return () => {
window.removeEventListener("popstate", handlePopState);
};
}, []);
const updateURL = (path) => {
window.history.pushState(null, "", path);
setCurrentPath(window.location.pathname); // ❌
};
return { updateURL, currentPath };
};
경로에 따른 알맞은 컴포넌트가 렌더링되게 하려면 popState 이벤트가 발생해야 한다.
하지만 updateURL()
함수 내에서 setCurrentPath()
로 상태를 업데이트해주면, popState 이벤트가 발생하지 않고, currentPath
상태값만 업데이트해주는 것이다.
그렇다면 updateURL()
함수를 어떻게 수정해야할까?
X 표시한 줄에서 직접 상태를 업데이트시켜주는 것이 아닌 popState 이벤트가 발생하도록 해줘야 한다.
new Event()
생성자를 이용해 이벤트를 만들고, dispatchEvent(event)
메소드를 사용해 이벤트를 발생시킬 수 있다.
Creating and triggering events
EventTarget.dispatchEvent()
const updateURL = (path) => {
window.history.pushState(null, "", path);
const event = new Event("popstate"); // 이벤트 생성
dispatchEvent(event); // 이벤트 트리거
};
이제 updateURL()
함수가 실행될 때마다 url 변경과 popState 이벤트가 발생하고, currentPath
가 업데이트되며 화면이 제대로 리렌더링되는 것을 확인할 수 있다.
react 뜯어보는 게 참 재밌는거 같아요. 로직 계속 봤는데도, 어렵네요 ㅠㅠ
훅으로 쓰면 내보낼 때 state 값이 아니라 순수하게 string으로 내보내는거같아요 신기하네요.