React와 History API 사용하여 SPA Router 구현하기

임동현·2023년 7월 5일
0
post-thumbnail

이번에 Vite를 이용하여 React + Typescript 프로젝트를 처음 생성해 봤다.

yarn create vite [프로젝트 명] --template react-ts

npm create vite@latest [프로젝트 명] --template react-ts

이번 원티드 7월 챌린지에 참여하면서 수업 실습 프로젝트가 Vite 라는 생소한걸 처음 써보게 되었다.

간단하게 프로젝트를 크게 다뤄본건 아니고 간단한 과제를 하면서 느낀점으론 작업하면서 Vite 가 CRA에 비해 정말 빨랐다. 정말 궁금해서 간단하게 검색하면서 알아본 결론은

  • Vite는 CRA 와 달리 웹팩이 아닌 esbuild 라는 번들링 프로그램을 사용한다.
  • CRA는 코드가 바뀌면서 모든 js 코드를 새로 번들링 하는데 , vite는 첫 번째 실행에서만 전체를 번들링 하고 이후에 변경된 부분만 새로 번들링 한다.

과제를 진행하는데 있어서 최근에 계속 Next.js 를 쓰다보니 과제에 대한 글을 제대로 읽지 않고 , 뭐야 그냥 Router 를 통해서 Page 이동을 하면되는거 잖아 하면서 쉽게 생각 했었다.

하지만 , React usenavigate() 을 사용하는게 아닌 History API 를 이용해 SPA Router 기능 구현이 목적인것이다.

먼저 main.tsx 코드 수정이 필요하다.

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)

index.html 파일을 열어봤을때 태그 안에

가 보여진다.
document.getElementById('root') as HTMLELement
html 요소에 id = root 를 찾아서 ReactDOM.createRoot() 함수를 사용해 해당 요소에
React DOM 을 렌더링 합니다.

초기 페이지 / , /about 페이지 2개의 페이지를 만들어야하는데

현재 main.tsx 파일은 App 구성 요소만 렌더링을 하고있다. URL 경로 기반으로 App , About 구성 요소로 전환 할수 있게 수정을 해야한다.

import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom/client";
// import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import App from "./App";
import About from "./About";
import "./index.css";

const Router = () => {
  const [route, setRoute] = useState(window.location.pathname);

  useEffect(() => {
    const handlePopstate = () => {
      setRoute(window.location.pathname);
    };

    window.addEventListener("popstate", handlePopstate);

    return () => {
      window.removeEventListener("popstate", handlePopstate);
    };
  }, []);

  switch (route) {
    case "/about":
      return <About />;
    default:
      return <App />;
  }
};

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
  <React.StrictMode>
    <Router />
  </React.StrictMode>
);

Router 구성 요소를 추가했다. useState Hook 을 사용하여 현재 경로를 보유하고 , useEffect Hook 을 사용하여 handlePopstate 함수는 popstate 이벤트가 발생할 때 마다 실행됩니다. 새 경로로 route 상태를 업데이트 합니다.

window.addEventListener("popstate" , handlePopstate) popstate 이벤트 리스너는 window 개체에 추가 됩니다. handlePopstate 는 이벤트가 실행될때마다 실행됩니다.

정리 함수 return 에서는 window.removeEventListener("popstate" , handlePopstate)
는 Router 구성요소가 DOM에서 마운트 해제되거나 다시 렌더링 되기 전에 이벤트 리스너를 제거하기 위해 실행됩니다. 이 잠재적인 메모리 누수를 방지하는데 있어서 중요합니다!

  switch (route) {
    case "/about":
      return <About />;
    default:
      return <App />;
  }

여기서 switch 문은 현재 경로를 기반으로 렌더링 할 구성 요소를 결정하는 데 사용됩니다. 경로가 "/about" 이면 About 컴포넌트를 렌더링 하고 있습니다. 다른 경로 같은 경우 현재는 2개의 페이지만 표현하기에 "/" App 컴포넌트가 렌더링이 됩니다.

import { useState } from "react";
import reactLogo from "./assets/react.svg";
import viteLogo from "/vite.svg";
import "./App.css";

function App() {

const navigateTo = (url: string) => {
  window.history.pushState(null, "", url);
  const popStateEvent = new PopStateEvent("popstate");
  window.dispatchEvent(popStateEvent);
};

return (
  <>
    <div>
      <a href="https://vitejs.dev" target="_blank">
        <img src={viteLogo} className="logo" alt="Vite logo" />
      </a>
      <a href="https://react.dev" target="_blank">
        <img src={reactLogo} className="logo react" alt="React logo" />
      </a>
    </div>
    <h1>Vite + React Main Home</h1>
    <p>Root</p>
    <button onClick={() => navigateTo("/about")}>About Page Click</button>
    <p className="read-the-docs">
      Click on the Vite and React logos to learn more
    </p>
  </>
);
}

export default App;
   const navigateTo = (url: string) => {
    window.history.pushState(null, "", url);
    const popStateEvent = new PopStateEvent("popstate");
    window.dispatchEvent(popStateEvent);
  };

과제 목적이 historyAPI 를 사용하는게 목적이기에 위와 같은 함수를 만들었다.

  • window.history.pushState(null, "" ,url);
    history 개체에 pushState 메서드를 사용하여 브라우저 기록에 새 항목을 추가합니다. 이는 새 URL 이 주소 표시줄에 표시되고 사용자가 브라우저의 뒤로 버튼을 사용하여 이전 URL 로 돌아갈 수 있음을 의미합니다. 그러나 이것은 페이지 로드를 트리거 하지않습니다.

  • const popStateEvent = new PopStateEvent("popstate");
    새로운 PopStateEvent 를 생성합니다. 이 유형의 이벤트는 일반적으로 사용자가 기록을 사용하여 다른 URL 로 이동할 때 브라우저에 의해 트리거 되지만 여기서는 수동으로 생성합니다.

  • window.dispatchEvent(popStateEvent)
    이벤트를 전달합니다. 즉 , 설정된 모든 popstate 이벤트 핸들러(Router 구성 요소의 main.tsx useEffect Hook 에 있는 popstate 이벤트 핸들러) 가 호출됩니다.

    위와 같은 함수가 button 을 클릭시 실행이 되면 route 상태를 업데이트하여 Router 구성 요소가 다시 렌더링되고 새 URL에 대한 올바른 구성 요소를 표시하도록 합니다.

화면을 녹화하고 보니 , url 부분이 짤려있고 페이지만 녹화되어져 있어서 아쉽지만
about 페이지에서도 함수는 동일하며 , url 위치 경로만 "/" 한다면 위와같은
결과물을 볼수있습니다 .

profile
프론트엔드 공부중

0개의 댓글