History API
History API는 history 전역 객체를 통해 브라우저 세션 히스토리에 대한 접근을 제공한다. 방문기록을 앞뒤로 탐색하고, 기록 스택의 내용을 조작할 수 있는 메서드와 속성을 제공해준다.
<script>
//이전 페이지로 이동
() => window.history.back()
//앞 페이지로 이동
() => window.history.forward()
//-Infinity < num < Infinity
//history 리스트의 현재 위치에서 num만큼 이동(음수가능, 0일 시 새로고침)
() => window.history.go(num)
</script>
pushState(stateObj, title, url)
replaceState(stateObj, title, url)
pushState는 현재 history 스택에 항목을 새로 추가하는 것이다.
"1> 2> 3> 4> 5" 의 스택이 있고, 현재 idx가 3에 위치해 있을 때 pushState를 사용하면
"1> 2> 3> x" 으로 스택이 변하게 된다.
replaceState는 history스택의 현재위치를 새 항목으로 변경하는 것이다.
"1> 2> 3> 4> 5"의 스택이 있고, 현재 idx가 3에 위치해 있을 때 replaceState를 사용하면
"1> 2> x> 4> 5"의 스택으로 변하게 된다.
전체 코드
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="index.css" />
<title>Document</title>
<script type="module">
function navigate(state) {
const h1 = document.querySelector("h1");
h1.textContent = state.path.toUpperCase();
const { path } = state;
const $root = document.getElementById("root");
switch (path) {
case "home":
$root.innerHTML = `<div id="homeDiv">homeDiv</div>`;
break;
case "about":
$root.innerHTML = `<div id="aboutDiv">aboutDiv</div>`;
break;
case "contact":
$root.innerHTML = `<div id="contactDiv">contackDiv</div>`;
break;
default:
$root.innerHTML = "";
break;
}
}
["home", "about", "contact"].forEach((path) => {
const button = document.querySelector(`#${path}`);
button.addEventListener("click", () => {
const state = { path };
history.pushState(state, "", path);
navigate(state);
});
});
const $replaceBtn = document.getElementById("replaceBtn");
$replaceBtn.addEventListener("click", () => {
const state = { path: "company" };
history.replaceState(state, "", "/company");
navigate(state);
});
window.addEventListener("popstate", (e) => {
navigate(e.state);
});
const $backBtn = document.querySelector("#backBtn");
const backBtnHandle = () => {
window.history.back();
};
$backBtn.addEventListener("click", () => backBtnHandle());
const $forwardBtn = document.querySelector("#forwardBtn");
const forwardBtnHandle = () => {
window.history.forward();
};
$forwardBtn.addEventListener("click", () => forwardBtnHandle());
const $goBtn = document.querySelector("#goBtn");
const goBtnHandle = () => {
const goNum = document.querySelector("#goNum").value;
window.history.go(goNum);
};
$goBtn.addEventListener("click", () => goBtnHandle());
</script>
</head>
<body>
<div>
<button id="home">Home</button>
<button id="about">About</button>
<button id="contact">Contact</button>
</div>
<h1></h1>
<div>
<button id="backBtn">back</button>
<button id="forwardBtn">forward</button>
<input id="goNum" />
<button id="goBtn">go</button>
</div>
<div>
<button id="replaceBtn">replaceState</button>
</div>
<div id="root"></div>
</body>
</html>
그렇다면, react-router-dom에서 useNavigate로 어떻게 SPA동작을 하는걸까?
function useNavigateUnstable() {
//...
let {
basename,
future,
navigator
} = React.useContext(NavigationContext);
//...
let navigate = React.useCallback(function (to, options) {
//...
if (typeof to === "number") {
navigator.go(to);
return;
}
let path = resolveTo(to, JSON.parse(routePathnamesJson), locationPathname, options.relative === "path");
//...
(!!options.replace ? navigator.replace : navigator.push)(path, options.state, options);
}, [basename, navigator, routePathnamesJson, locationPathname, dataRouterContext]);
return navigate;
}
해당코드는 react-router 라이브러리의 코드이다.
사용자가 입력한 to가 숫자일때는 history.go를 사용하고, early return을 하여 후 작업을 진행하지 않는다.
숫자가 아닐경우엔 resolveTo함수로 입력한 값을 상대주소에서 절대주소로 변경하는 작업을 한다.
그리고 options의 replace값에따라 history.replaceState, history.pushState를 실행하여
SPA로 페이지이동을 구현하는 것을 알 수 있다.
다만 저 navigator가 왜 history의 작업을 하는지는 찾지 못하였다.
function MemoryRouter(_ref3) {
let {
basename,
children,
initialEntries,
initialIndex,
future
} = _ref3;
//...
return /*#__PURE__*/React.createElement(Router, {
basename: basename,
children: children,
location: state.location,
navigationType: state.action,
navigator: history,
future: future
});
}
정확하게 어디서 NavigateContext를 선언하고 변경하는지는 못찾았지만, 이러한 함수에 의해 navigator가 history로 덮어씌워지고, 따라서 우리는 navigator.go(), navigator.replace(), navigator.push()와 같은 history API를 사용할 수 있다.
궁금증
브라우저의 이전, 다음페이지는 linked list로 관리된다고 알고있었는데 맞는지?
back으로 linked list의 이전으로 이동하고 해당 노드의 포인터를 바꾸는 것인지?
'2차 공부 > TIL' 카테고리의 다른 글
24.08.28 React Concurrent Feature / useSyncExternalStore (0) | 2024.08.28 |
---|---|
24.08.27 react의 useSyncExternalStore 훅 알아보기 (0) | 2024.08.27 |
24.08.26 Next JS CSS (0) | 2024.08.26 |
24.08.26 Next JS 병렬 fetching / 에러발생페이지 (0) | 2024.08.26 |
24.08.26 Next JS Data Fetching / loading (0) | 2024.08.26 |