React에서 라우팅할 때 react-router-dom
라이브러리를 사용한다.
그래서 이 라이브러리의 동작 방식을 까보기로 했다.
import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import { Main, Page1, Page2, NotFound } from "../pages";
import { Header } from ".";
const Router = () => {
return (
<BrowserRouter>
<Header />
<Routes>
<Route path="/" element={<Main />} />
<Route path="/page1/*" element={<Page1 />} />
<Route path="/page2/*" element={<Page2 />} />
<Route path="/*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
};
export default Router;
대충 이런식으로 구조가 되있기 때문에 처음 까볼 곳을 BrowserRouter
!
// react-router-dom/index.tsx
export function BrowserRouter({
basename,
children,
window,
}: BrowserRouterProps) {
let historyRef = React.useRef<BrowserHistory>();
if (historyRef.current == null) {
historyRef.current = createBrowserHistory({ window, v5Compat: true });
}
let history = historyRef.current;
let [state, setState] = React.useState({
action: history.action,
location: history.location,
});
React.useLayoutEffect(() => history.listen(setState), [history]);
return (
<Router
basename={basename}
children={children}
location={state.location}
navigationType={state.action}
navigator={history}
/>
);
}
// router/history.ts
export function createBrowserHistory(
options: BrowserHistoryOptions = {}
): BrowserHistory {
function createBrowserLocation(
window: Window,
globalHistory: Window["history"]
) {
let { pathname, search, hash } = window.location;
return createLocation(
"",
{ pathname, search, hash },
// state defaults to `null` because `window.history.state` does
(globalHistory.state && globalHistory.state.usr) || null,
(globalHistory.state && globalHistory.state.key) || "default"
);
}
function createBrowserHref(window: Window, to: To) {
return typeof to === "string" ? to : createPath(to);
}
return getUrlBasedHistory(
createBrowserLocation,
createBrowserHref,
null,
options
);
}
history API를 사용해서 구현하는 걸 알 수 있었다.
import { render } from "../Kreact";
export default function createRouter(root, routes = []) {
const history = window.history;
const routeMap = new Map();
routes.forEach(({ pathname, element }) => {
routeMap.set(pathname, element);
});
function push(pathname, state) {
history.pushState(state, null, pathname);
_render(root, pathname);
}
window.addEventListener('popstate', () => {
_render(root, window.location.pathname);
});
function _render(root, pathname) {
const element = routeMap.get(pathname);
if (!element) throw new Error('NOT FOUND');
root.innerHTML = '';
render(root, element);
}
return {
push,
}
}
createRouter
를 만들어보았다. react-router-dom의 createBrowserRouter
와 비슷하게 구현했다.
// react-router-dom의 createBrowserRouter
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
loader: rootLoader,
children: [
{
path: "team",
element: <Team />,
loader: teamLoader,
},
],
},
]);
// 내가 만든 createRouter
export const router = createRouter(document.getElementById('root'),
[
{
pathname: '/',
element: Home,
},
{
pathname: '/about',
element: About,
},
{
pathname: '/contact',
element: Contact,
},
]
);