2차 공부/TIL

24.08.26 챌린지반 아티클 -3 / 자바스크립트의 History API

공대탈출 2024. 8. 27. 00:20

배포페이지

깃허브

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의 이전으로 이동하고 해당 노드의 포인터를 바꾸는 것인지?