Vanilla TypeScript로 SPA 라우팅 기능 구현해 보기

김가희·2024년 7월 16일

이 글의 목적은 Vanilla JavaScript (TypeScript)를 사용하여 SPA 라우팅 기능을 구현함으로써, SPA 라우팅의 기본 원리를 학습하는 것이다.


SPA 라우팅

Single Page Application 라우팅은 사용자가 웹 사이트의 페이지를 탐색할 때 페이지를 새로고침하지 않고도 동적으로 콘텐츠를 로드하고 표시하는 방식이다. 웹 페이지가 서버로부터 최초에 한 번 로드된 후, 추가적인 페이지 로드를 요구하지 않고 클라이언트 측에서 모든 뷰 변경을 처리한다.

작동 원리
1. 초기 로딩: 사용자가 웹 애플리케이션에 처음 접속하면, 서버는 하나의 HTML 파일(index.html)과 관련 CSS 및 JavaScript 파일들을 제공한다. 이후의 모든 작업은 JavaScript를 통해 이루어진다.
2. URL 변화 감지: SPA 라우팅을 주로 History API를 사용하여 브라우저의 주소 변경을 감지한다. pushState와 같은 메소드를 사용하여 URL을 변경하지만, 실제로 서버에 새로운 요청을 보내지는 않는다.
3. 동적 컨텐츠 로딩: URL이 변경되면, JavaScript는 해당 URL에 매핑된 컴포넌트 등을 동적으로 렌더링한다.


구현 방법

싱글톤 패턴 적용

싱글톤 패턴을 사용하여, 애플리케이션 전역에서 단 하나의 인스턴스만을 생성하고 관리한다. 이렇게 함으로써 모든 컴포넌트에서 동일한 라우터 인스턴스를 공유하고, 상태 일관성을 유지할 수 있다. 싱글톤 패턴을 적용하지 않으면 모든 컴포넌트에서 라우터를 새로 생성해야 하기 때문에 비효율적이다.

// src/Router.ts
private static instance: Router;
routes: any = {};

constructor() {
  window.addEventListener('popstate', this.resolve.bind(this));
}

public static getInstance(): Router {
  if (!Router.instance) {
    Router.instance = new Router();
  }
  return Router.instance;
}

라우트 등록

사용자가 정의한 각 경로와 해당 경로에 렌더링될 페이지 컴포넌트를 매핑한다. routes 객체를 이용하여 경로를 키로, 페이지 컴포넌트의 생성자를 값으로 저장한다.

// src/Router.ts
add(path: string, page: any) {
  this.routes[path] = page;
}
// src/Router.ts
document.addEventListener('DOMContentLoaded', function () {
  const router = Router.getInstance();
  router.add('/', MainPage);
  router.add('/login', LoginPage);

  router.resolve();
});

라우트 렌더링

현재 브라우저의 URL에 해당하는 페이지를 찾아 렌더링해 준다. 브라우저의 location 객체에서 pathname을 가져와 현재 경로를 확인한다. 매핑된 페이지가 있다면 해당 페이지의 인스턴스를 생성하고, render 메서드를 호출하여 페이지를 렌더링한다. 만약 매핑된 경로가 없다면, 선택적으로 404 오류 페이지를 렌더링할 수 있다.

// src/Router.ts
resolve() {
  const { pathname } = window.location;
  const route = this.routes[pathname] || this.routes[404];
  if (route) {
    const page = new route();
    page.render();
  }
}

URL 내비게이션

HTML5 History API의 pushState 메서드를 사용하여 브라우저의 히스토리 스택에 새로운 상태를 추가하고 URL을 변경한다. URL 변경 후, resolve 메서드를 호출하여 새 URL에 맞는 페이지를 렌더링한다.

// src/Router.ts
navigate(path: string) {
  window.history.pushState({}, path, window.location.origin + path);
  this.resolve();
}

실행 화면

아래처럼 메인 페이지에서 로그인 페이지로 이동하는 버튼을 만들어 실행해 보았다.

export default class MainPage extends Component {
  constructor(props: ComponentProps) {
    super(props);
  }

  render() {
    const element = document.getElementById('root');
    if (element) {
      element.innerHTML = `
        <div>
          <button id="go-to-login">Go to Login</button>
        </div>
      `;
      document.getElementById('go-to-login')?.addEventListener('click', () => {
        const router = Router.getInstance();
        router.navigate('/login');
      });
    }
  }
}


배운 점

  • 직접 라우터를 구현함으로써, SPA의 라우팅이 내부적으로 어떻게 작동하는지 이해할 수 있었다.
  • 라우터 인스턴스를 전역적으로 하나만 유지함으로써 애플리케이션 전체에서 상태 일관성을 보장하는 방법을 배웠다. 이는 리소스를 효율적으로 사용할 수 있게 해 주고, 예상치 못한 오류를 방지하는 데 중요하다.

0개의 댓글