[ Javascript ] 바닐라 자바스크립트로 SPA 만들기

꾸개·2024년 1월 15일
0
post-thumbnail
post-custom-banner

개요


Single-Page-Application(SPA)은 페이지 이동 시 새로고침 되지 않고 빠르게 이동하는 페이지 기법을 일컫는 말로 React와 CSR이 유행할 때 성행했던 기법이다. 대표적으로는 페이스북, 인스타그램이 많이 사용하는 기법으로 알려져있다.

React는 이런 SPA 기법을 기본적으로 제공하는 라이브러리이다. 많은 개발자들이 자사 서비스에 도입했고 학습하였고 나 또한 React를 학습하며 비교적 손쉽게 프로그래밍을 할 수 있었다.

하지만, React를 슬슬 대체하기 시작하는 Next를 보며 React에 너무나 익숙해진 나는 살짝 걱정이 되기 시작했다. 물론 둘의 개발방식은 거의 흡사하다. React를 할 줄 알면 Next는 금방 배울 수 있다. 하지만, 아예 React / Next를 대체하는 라이브러리가 나온다면? Vue 혹은 다른 프레임워크나 바닐라 자바스크립트가 다시 유행한다면? 내 근간이 흔들릴 것이라고 생각이 들었다.

따라서, 왜 이 라이브러리 / 프레임워크를 쓰는지 역으로 불편함을 겪으면서 원리를 파악해보려고 한다.


SPA 원리


SPA의 가장 큰 특징은 하나의 페이지처럼 동작하는 것인데, 이 문장에 답이 숨어있었다. 정말로 한 페이지만 보여주는 것이다. 실제로 cra로 리액트 프로젝트를 새로 만들었을 때 프로젝트 폴더를 살펴보면 index.html로 하나의 페이지밖에 없다. 모든 컨텐츠들은 #root에 들어가는것이다.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

#root 안에 들어가는 자바스크립트들은 모두 모듈화 / 컴포넌츠화 되서 렌더링하게 된다.

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
  <React.StrictMode>
    <App />            // <== 여기에 컴포넌츠들을 추가
  </React.StrictMode>,
  document.getElementById('root') // <== #root를 참조
);

reportWebVitals();


Javascript page

Single-Application-Page

바닐라 자바스크립트는 페이지를 개별로 만들어서 페이지를 이동할 때마다 보여주지만,
SPA는 페이지를 이동할때 예전 컨텐츠를 지우고 새롭게 작성한다. 컨텐츠는 달라지지만, 보이는 것은 화이트보드처럼 하나이기 때문에 SPA라고 불리울 수 있는 것이다. 아마, 이 개념을 만든 사람은 정말로 페이지가 하나이기 때문에 이러한 정의를 내린 것 같다.


바닐라 자바스크립트로 만들기


Router 설정

본격적으로 만드려면 리액트의 index.html처럼 메인으로 보여줄 html페이지를 만들어주고 그 안에서 동작할 모듈을 넣어준다. 이 모듈은 컨텐츠를 지우고 불러오는 과정을 하는 Router이다.

<!-- index.html -->

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SPA</title>
</head>
<body>
    <div id="root">
        <script type="module" src="/Router.js"></script>
    </div>
</body>
</html>

그리고 Router.js를 정의해준다.

//Router.js

const routes = [
  {
    path: "/",
    location: "mainpage",
  },
  {
    path: "/mypage",
    location: "mypage",
  },
];

const App = () => {
  const pageMatches = routes.map((route) => {
    return {
      route: route,
      isMatch: window.location.pathname === route.path,
    };
  });

  let match = pageMatches.find((pageMatch) => pageMatch.isMatch);
  console.log(match);
};

App();

routes배열의 객체들중 pathwindow.location.pathname와 일치하다면 pageMatches에서 true를 가지게 되고 그 객체는 match에 할당되어 콘솔에 찍히게 될 것이다.

잘 찍혀있는것이 확인되었다. 이로써 현재 페이지와 일치하는 페이지의 정보를 불러 올 수 있게 되었다. 그러나 다른 페이지로 직접 url주소를 변경할 경우 해당 html을 불러온다. 이는 바로 http요청을 보내서 실제 html페이지를 요구한다. 다른 방법을 사용해야한다.

History API

위에 언급된 내용을 조작할 수 있는것이 History API이다. 여기서 pushState메서드를 이용해 실제로는 해당 페이지를 방문하지 않지만, url주소에 삽입함으로써 해당 페이지를 방문한것처럼 보이게 할 것이다.

<!-- index.html -->

<body>
    <button class="mainbutton">Mainpage</button>
    <button class="mybutton">Mypage</button>
    <div id="root">
        <script type="module" src="/Router.js"></script>
    </div>
</body>
//Router.js
...

const changeUrl = (requestedUrl) => {
  history.pushState(null, null, requestedUrl);
  Router();
};

window.addEventListener("click", (e) => {
  if (e.target.classList.contains("mainbutton")) {
    changeUrl("/");
  } else if (e.target.classList.contains("mybutton")) {
    changeUrl("/mypage");
  }
});

html에 버튼을 추가해서 해당 버튼을 누를때 해당 페이지로 이동하게끔 만들었다.
그 다음, Router.js에서 history.pushState()를 통해 매개변수로 받은 문자열로 url을 바꾸게끔 만들었고 버튼을 눌렀을 때 해당 페이지를 렌더( Router() )하게끔 구현했다.

mypage버튼을 누르면 url이 잘바뀌고 페이지의 위치가 mypage인 것도 확인되었다!


결론

직접적으로 http요청을 하지않고 history.pushState()을 이용해 간접적으로 url을 바꾸고 바뀐 url을 router가 감지하고 해당 페이지의 데이터를 로드하게끔 하는것이 SPA의 방식이라고 정리할 수 있겠다! 물론 React 라이브러리는 이것보다 복잡하고 섬세할테지만, 근본적인 동작방식과 실제로 구현해보면서 React보다 불편한 점을 체감하면서 정말 많은 공부가 되었다.

profile
내 꿈은 프론트 왕
post-custom-banner

0개의 댓글