

history API란? 에서 history API란 무엇인지, 또 어떻게 사용하는 지 알아보았다. 이번에는 history API를 이용해 SPA를 구현하고자 할 때, 꼭 신경써야 하는 부분 3가지를 알아보자.
<body>
    <div id="container"></div>
    <a class="LinkItem" href="/study-list">study list</a>
    <a class="LinkItem" href="/play-list">play list</a>
원래 브라우저에서 a 태그를 클릭했을 때의 기본 동작은, href에 연결된 url로의 페이지 이동이다. 즉, 내 local client 기준으로 생각하면, 위 코드에서 첫번째 a 태그를 클릭했을 때  http://localhost:3000/study-list/index.html 라는 파일을 찾으려고 한다. 
그런데 우리는 원하는 동작은 url만 /study-list로 변경하고 이에 따라 페이지의 일부분을 변경하는 것이다. 또한, 현재 study-list/index.html 파일을 우리가 가지고 있는 것이 아니기 때문에, 404 error가 발생한다.
따라서, pushState를 통해 SPA를 구현하고자 할 때, a 태그를 클릭했을 때 위와 같은 a 태그의 기본 역할이 실행되지 않도록 우선 해줘야한다. 이는 이벤트 버블링을 이용해 구현할 수 있다. 
<a class="LinkItem" href="/study-list">study list</a>
<a class="LinkItem" href="/play-list">play list</a>
<script>
  window.addEventListener("click", (e) => {
    if (e.target.className === "LinkItem") 
      e.preventDefault();
    }
  });
</script>
여기서 클릭이벤트가 이벤트 버블링되면서 a 태그에서 window 까지 올라갈텐데, className이 LinkItem인 a태그가 클릭되었다면, a 태그가 이동하지 않도록 preventDefault()를 실행해 페이지 이동을 방지할 수 있다. 
이제 a 태그가 클릭되었을 때, 링크로 이동하지 않고, url도 변경되지 않는다. 이제 a 태그가 클릭되었을 때, hisrtory state를 쌓아서 url만 변경해주려고 한다.  
window.addEventListener("click", (e) => {
  if (e.target.className === "LinkItem") 
    e.preventDefault();
    const { href } = e.target; // http://localhost:3000/study-list
    const path = href.replace(window.location.origin, ""); // http://localhost:3000을 없애주고 /study-list만 가져와준다.
    history.pushState(null, null, path);
    route();
  }
}); 
history.pushstate를 이용해 a 태그의 href 속성 값에서 path를 구하고 path를 url로 설정해주면, a 태그를 클릭했을 때 http://localhost:3000/study-list로 url이 바뀌고 화면은 이동하지 않게 된다. 
하지만 여기서, 만약 새로고침을 누른다면, 브라우저는 다시
http://localhost:3000/study-list/index.html을 찾으려고 하고, 404 error를 발생시킨다. 따라서, 이러한 상황일 때, 루트에 있는 index.html로 돌려주는 처리가 필요하다.현재 폴더를 기준으로 터미널을 실행 후,
npx serve - s를 실행하면 404 error가 발생하면, 루트 폴더의 index.html로 이동시켜주는 동작을 지원해준다.=> history API를 쓰는 SPA 방식의 라우팅을 지원하는 모든 라이브러리는 이런 문제를 가지고 있기 때문에 적절히 대응해줘야 한다 !
a 태그를 클릭할 때 마다 history state를 변경해줌으로써 url을 변경했다면 이제 이 url을 기준으로 어떤 컴포넌트를 화면에 그릴지 결정해줘야 한다! 
현재 location.pathname을 기준으로 컴포넌트를 그려주는 route() 함수를 정의했다. 
function route() {
 const { pathname } = location;
 const container = document.querySelector("#container");
 if (pathname === "/") {
   container.innerHTML = "Todo List!!";
 } else if (pathname === "/study-list") {
   container.innerHTML = `<h1>공부할 것들</h1>`;
 } else if (pathname === "/play-list") {
   container.innerHTML = `<h1>놀거리들</h1>`;
 }
}
각 url은 a 태그의 클릭을 통해서도 변경될 수 있지만, 뒤로가기, 앞으로가기 버튼의 클릭, 콘솔에서 forward(), back(), go()를 통해서도 변경될 수 있고, 이러한 경우에도 화면을 다시 그려줘야 한다. popstate event를 이용해 구현해줄 수 있다.
window.addEventListener("popstate", () => route());
새로고침 버튼이나,
history.go(0)를 실행했을 때는 url의 변경이 일어나지 않기 때문에 popstate event가 발생되지 않는다.
전체 코드는 다음과 같다.
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="container"></div>
    <a class="LinkItem" href="/study-list">study list</a>
    <a class="LinkItem" href="/play-list">play list</a>
    <script>
      function route() {
        const { pathname } = location;
        const container = document.querySelector("#container");
        if (pathname === "/") {
          container.innerHTML = "Todo List!!";
        } else if (pathname === "/study-list") {
          container.innerHTML = `<h1>공부할 것들</h1>`;
        } else if (pathname === "/play-list") {
          container.innerHTML = `<h1>놀거리들</h1>`;
        }
      }
      route();
      window.addEventListener("click", (e) => {
        if (e.target.className === "LinkItem") {
          e.preventDefault();
          const { href } = e.target; // http://localhost:3000/study-list
          const path = href.replace(window.location.origin, ""); // http://localhost:3000을 없애주고 /study-list만 가져와준다.
          history.pushState(null, null, path);
          route();
        }
      }); 
      window.addEventListener("popstate", () => route());
    </script>
  </body>
</html>
개인 프로젝트인 노션 프로젝트에 실제로 history API를 이용해 SPA를 구현했는지에 대한 내용이다.
이 프로젝트에서는 보여질 수 있는 페이지를 크게 2가지로 나누었다.
선택된 document가 없는 메인 페이지
=> ex) http://localhost:3000

특정 document에 대한 페이지
=> ex) http://localhost:3000/88179

그래서 현재 선택된 document의 Id를 바탕으로 페이지의 라우팅을 관리하고자 했고, 이 data를 App 컴포넌트의 state인 selectedDocumentId로 관리했다. 
this.route = () => {
  // 현재 url의 pathname으로 selectedId를 변경
  const { pathname } = location;
  if (pathname === "/") {
    this.setState({ selectedDocumentId: null }); // editPage를 다시 렌더링
    this.render(); // sideBar 다시 렌더링 
  } else {
    const [, documentId] = pathname.split("/");
    this.setState({ selectedDocumentId: documentId });
    this.render();
  }
};
pushState(null, null, selectedDocumentId)pushState(null, null, selectedDocumentId)pushState(null, null, "/")app 컴포넌트에서 route() 함수 실행