티스토리 뷰

728x90
반응형

 

 

SPA를 JavaScript로 구현하는 프로젝트를 진행하면서 url 변경 이벤트를 감지해야 하는 경우가 발생했다. 일반적으로는 pushState로 URL 변경 이후, router path에 해당하는 컴포넌트를 render 해준다. 아래와 같다.

const navigate = path => {
  window.history.pushState({}, null, path);
  render(element, routes[path]);
};

const render = (elem, Component) => {
	elem.innerHTML = Component;
}

 

그런데 SPA를 구현하면서 우리는 URL 변경에 초점을 두고 개발을 진행하게 됐다. 즉, URL 변경이 일어나면 URL을 파싱 해 router에 해당하는 path의 컴포넌트를 렌더링 하게 했다. 하지만 Web API에서는 'popstate' 이벤트만을 제공하고 있었다. 왜 pushState, replaceState 이벤트는 존재하지 않는지 잘 모르겠다. 이는 라이브러리를 사용해 쉽게 해결할 수 있었다.

 

history-events

Adds missing window.history events onpushstate, onreplacestate and onchangestate.. Latest version: 1.0.4, last published: 7 years ago. Start using history-events in your project by running `npm i history-events`. There are 5 other projects in the npm regis

www.npmjs.com

 

해당 라이브러리를 npm을 통해 설치 후, 아래와 같이 사용하면 된다.

const history = require('history-events');

window.addEventListener('changestate', ()=>{
 // write you want to do
})

 

 

다른 방법 구상

프로젝트가 끝난 이후, 라이브러리를 쓰는 방법 말고 다른 방법은 없을까 고민을 해보게 됐다. 다양한 방법들을 찾아보고 실패를 했다. 생각보다 레퍼런스가 없고, 방법들이 어려워 힘들었다. 같은 문제를 겪는 사람들에게 도움이 될까 작성해본다.

Mutation Observer

DOM tree의 변경을 감지하는 이벤트이다. 이 글을 보고 시도를 해보게 됐다. DOM tree의 변경을 감지하는 객체였다. 이를 통해서 url 변경이 일어날 때마다 실행을 시키려 했다. 하지만 JS 파일이 실행이 되고, 1회밖에 감지를 하지 못했다. 감지를 지속해서 시키기 위해선 결국 일정한 이벤트 등록이 필요했다.

setInterval

setInterval을 통해 계속 URL을 감지하는 방법이다. 물론 사용하면 안 된다. 웹 앱에서 계속 URL을 확인하는 방식이라, 규모가 커질수록 성능의 저하가 클 것이다.

CustomEvent

이벤트 핸들러를 직접 만들어보는 것도 고민해봤다. MDN을 참고해서 만들어보려 했다. 브라우저의 이벤트를 직접 만드는 방법이라고 생각했지만, 기존의 이벤트를 활용한 커스터마이징 이벤트 핸들러를 만드는 방법이었다. 결국 url 변경을 감지해야 하는데, CustomEvent로는 불가능하다고 생각했다.

pushState 이벤트 덮어쓰기

가장 좋은 방법이었다. 그 이유는 해당 프로젝트에서는 pushState만을 사용해서 url을 변경해줬기 때문에 해당 이벤트를 덮어 씌워서 원하는 동작을 하게 만들었다. 이 글을 참고했다. 코드는 다음과 같다.

(function(history){
    var pushState = history.pushState;
    history.pushState = function(state) {
        if (typeof history.onpushstate == "function") {
            history.onpushstate({state: state});
        }
        // ... whatever else you want to do
        // maybe call onhashchange e.handler
        return pushState.apply(history, arguments);
    };
})(window.history);

 

history-events 라이브러리도 코드를 완벽하게 이해하기 어렵지만, pushState, replaceState 이벤트가 발생하면 changestate 이벤트를 같이 발생시켜주는 방식으로 보인다.

 

위의 코드를 추가해 pushState 함수를 사용하면 바뀐 URL로 렌더링을 하게 바꿔줬다. 하지만 2번을 클릭해야 렌더링이 정상적으로 되는 것을 확인할 수 있었다. `window.location`은 최신화가 되지 않았기 때문인데 정확한 이유는 알아내지 못했다. pushState는 Location 객체까지 변경을 시키지는 않는다고 이해를 하면 될 것 같다.

그래서 아래와 같이 해줬을 때도 오류가 발생했다.

(function (history) {
  var pushState = history.pushState;
  history.pushState = function (state) {
    if (typeof history.onpushstate == 'function') {
      history.onpushstate({ state: state });
    }
     // ... whatever else you want to do
    // maybe call onhashchange e.handler
	render(App());
    return pushState.apply(history, arguments);
  };
})(window.history);

 

이를 해결하기 위해서 pushState 함수를 사용할 때, 첫 번째 인수로 state를 전달할 수 있었다. 이를 통해 새로운 세션의 url 정보를 state를 통해 전달하고, 이를 pushState 함수 내에서 활용하는 방법을 생각했다. 전달된 state를 보고 currentPageUrl의 변수에 할당해 App에 인수로 넘겨줬다.

예외처리가 발생해 까다로워지지만, 라이브러리를 사용하지 않아도 동작하게 만들 수 있었다.

// pushState 부분
history.pushState(
        { url: `/detail/${e.target.closest('li').dataset.postId}` },
        '',
        `/detail/${e.target.closest('li').dataset.postId}`
      );
    }
    
// pushState 이벤트 조작
(function (history) {
  var pushState = history.pushState;
  history.pushState = function (state) {
    if (typeof history.onpushstate == 'function') {
      history.onpushstate({ state: state });
    }
     // ... whatever else you want to do
    // maybe call onhashchange e.handler
    currentPageUrl = state?.url; // 옵셔널 체이닝
    if (currentPageUrl) render(App(currentPageUrl));
    return pushState.apply(history, arguments);
  };
})(window.history);

 

 

 

느낀 점

해결하고 나니 처음부터 pushState를 할 때, 렌더링을 해주는 게 편했겠다는 생각을 했다. 단순하게 구현이 되기 때문이다. 무엇이 정답인지는 모르겠지만, 직접 구현을 해보면서 설계를 처음에 잘해야겠다는 생각을 했다. SPA의 동작 방식을 바꾸면 전체 페이지의 코드를 수정해야 했다. 팀 프로젝트인 만큼 다른 사람의 코드를 건들기도 어려웠다. 때문에 설계의 중요성을 깨달았다. best practice를 찾아보고 구현을 했으면 더 가독성이 좋은 앱으로 만들 수 있었겠지만, 새로운 방법을 통해 다양한 Web API와 라이브러리의 소스코드를 학습할 수 있었다. 프론트엔드가 진입장벽은 낮다지만 정말 알아야 하는 것이 많다는 생각을 한다... 꾸준한 학습으로 지식을 증진시켜나가야겠다.

 

 

📕 Rerference :

https://phpcoder.tech/detect-url-change-in-javascript-without-refresh/

https://developer.mozilla.org/ko/docs/Web/API/History/pushState

https://stackoverflow.com/questions/34999976/detect-changes-on-the-url

https://stackoverflow.com/questions/4570093/how-to-get-notified-about-changes-of-the-history-via-history-pushstate

728x90
반응형

'TIL(Today I Learn)' 카테고리의 다른 글

VScode 줄바꿈이 적용이 되지 않는 오류  (0) 2022.06.08
[JS] CORS 깊게 이해하기  (0) 2021.12.28
[JS] this에 대해서  (0) 2021.12.11
[JS] 프로토타입이란?  (0) 2021.12.06
Pollyfill이란? Babel이란?  (2) 2021.11.07
댓글