프론트엔드

자바스크립트로 브라우저 라우팅 구현하기

CodeBoyEd 2022. 1. 2. 22:50

브라우저 라우팅 구현하기

리액트를 사용하지 않고, 타입스크립트로 코드를 구현하면서 라우팅 역시 직접 구현해야 한다는 것을 깨달았습니다.

라우팅은 어떤 작업일까? 라는 고민을 하게 됐고 제가 생각한 기능은 다음 두 가지 입니다.

 

  1. path 값에 해당하는 page가 렌더링 되야 한다.
  2. path 값의 흐름이 저장되어 있어서, 뒤로가기나 앞으로가기가 가능해야 한다.

 

제가 생각한 라우팅은 다음과 같습니다. 라우팅을 하는 path 값을 상태(state)로서 관리하는 것입니다. 그리고 실제 URL path 값을 state 와 일치 시키도록 history API를 적용하였습니다.

 

 

실행 로직

  • path 상태 값은 앞 포스팅에서 구현했던, 전역 store에서 관리합니다.
  • 가장 상위 컴포넌트에서 path 상태값에 따라 mount 되는 하위 컴포넌트를 갈아끼워줍니다.
  • 하위 컴포넌트를 mount 하기 전에, history API 를 활용하여 URL path 값을 바꿔줍니다.

조금 더 구체적으로 실제 라우팅이 필요한 상황에 대해서 생각해보겠습니다.

  • 페이지 전환 로직이 실행된 경우
  • 사용자의 URL 입력 시
  • 브라우저에서 뒤로 혹은 앞으로 가기 버튼이 눌러졌을 경우
  •  

1. 페이지 전환 버튼이 클릭 됐을 때

전환돼야 하는 페이지로 path 상태 값이 바뀌어야 합니다.

 

store.dispatch(setRouteAction(mainPath, subPath, true));

 

위와 같이 store에 접근하여 dispatch 함수를 사용하여 바꿔주면 됩니다.

이때 상태 값이 바뀌면 해당 하위 컴포넌트를 mount 하기 전에 history API를 통해서 path를 바꾸어주는 과정이 선행되야 합니다. 이를 코드로 나타내면 다음과 같습니다.

 

mounted(): void {
    const $main: HTMLElement = $(this.$target, '.main');
    const route = store.getState('route');
    const { mainPath, subPath, popState } = route;
    switch (mainPath) {
      case 'home':
        if (!popState)
          history.pushState({ mainPath, subPath }, 'homepage', '/home');
        new Home($main);
        break;
      case 'test':
        if (!popState)
          history.pushState({ mainPath, subPath }, 'testpage', '/test');
        new Test($main);
        break;
      case 'type':
        const path = !subPath ? '/type' : `/type/${subPath}`;
        if (!popState)
          history.pushState({ mainPath, subPath }, 'typepage', path);
        new Type($main, { value: subPath });
        break;
      default:
        history.replaceState(
          { mainPath: 'home', subPath },
          'homepage',
          '/home'
        );
        new Home($main);
        break;
    }
  }

 

이때 popState 변수로 한 번 경우의 수를 필터링 하는 이유는 뒤에서 설명할 뒤로가기/앞으로가기 버튼을 클릭하는 경우를 구별하기 위함 입니다.

위의 mounted() 함수에서는 history.pushState() 를 이용해 URL history를 기록하고, path에 해당하는 하위 컴포넌트를 적재합니다. 이때 history를 기록할 필요 없이 path만 바뀌면 되는 home 화면의 경우는 history.replaceState() 를 이용합니다.

 

2. 사용자가 특정 path를 입력한 경우

 

// 예시 /test
https://www.woongil.com/test 

 

사용자가 브라우저에 특정 Path를 입력해서 요청을 보낸 경우에는 서버를 거치게 됩니다.

서버에서 Path 에 상관 없이 정적 파일을 내려줄 경우, 정적 파일은 Path 경로를 읽어서 해당하는 Page를 화면에 렌더링 해줘야 합니다.

따라서 파일 실행 순서 앞 부분에 다음과 같은 코드를 작성했습니다.

 

const [mainPath, subPath] = window.location.pathname.split('/');

store.dispatch(setRouteAction(mainPath, subPath, false)); 

 

위와 같은 코드를 추가하여, 정적 파일이 렌더링 되기 전에 전역 store 에 있는 path 값을 URL에서 읽어와 설정하게 됩니다. ( 위 양식을 벗어난 path가 입력될 경우 default 값인 home 화면으로 라우팅 됩니다. )

이렇게 설정하면, 렌더링 과정에서 전역 store의 path state가 URL path로 읽혀서 해당 페이지가 하위 컴포넌트로 렌더링됩니다.

 

3. 브라우저에서 뒤로가기 혹은 앞으로가기 버튼 클릭 시

 

브라우저 이벤트의 경우에는 window 전역 객체에 addEventListener 를 통해 감지할 수 있습니다. 브라우저의 뒤로가기, 혹은 앞으로가기 이벤트가 발생한 경우 history 의 순서대로 URL 이 변할 것 입니다. 이때, store에 있는 path 상태 값을 history state의 path 값과 일치 시켜 주면 됩니다.

 

window.onpopstate = (e: PopStateEvent) => {
  const { mainPath, subPath } = e.state;
  store.dispatch(setRouteAction(mainPath, subPath, true));
};

 

이때, Action 3번째 인자로 true를 주는 이유는 뒤로 가기나 앞으로 가기의 이벤트의 경우,

history pushState가 일어나서는 안되며 path 상태 값에 따라서 알맞는 컴포넌트만 렌더링 되야 합니다. 때문에 위와 같은 구별이 필요합니다.