2015년 6월 18일 목요일

history 객체를 사용한 client router 구현

지난 Meteor Meetup에서 iron-router를 써야하냐 말아야하냐에 대한 논의가 있었는데
MDG(Meteor Development Group)에서 딱히 router에 대한 명확한 입장이 없어
SPA(Single Page Application)에서 클라이언트쪽 라우터에 대해 정리해 보려고 한다.
전반적인 내용은 여기에 -> http://html5.clearboth.org/history.html

기존의 Web과 다르게 SPA에선 화면간 이동시 전체 화면을 다시 그리지 않는다.
그래서 기존 독립 실행 어플리케이션(데스크톱 프로그램/모바일 앱 등)처럼 부드럽고 빠른 기능 전환이 가능한데 대신 기존의 뒤로 가기 버튼을 무심코 눌렀을 때 해당 어플리케이션에서 이탈하곤 한다.

그래서 실제로 URL이동을 하지 않아도 기능상 다른 화면으로 이동하였을 경우 임의로 URL을 생성하여 history 객체에 넣는 방식으로 구현한다.
전체적인 그림은 아래와 같은데

단순한 전략이다. push 와 pop.
이동이 일어나는 시점에 history.pushState( <state>, "<title>", "<url>"); 로 쌓아두고 뒤로 가기/앞으로 가기를 브라우저에서 실행할 경우 popstate 이벤트를 처리하면 된다.
window.addEventListener("popstate", function (event) {
  // event.state 를 참조
  // 이 시점은 이미 pop을 완료한 상태이므로 location을 살펴보면 이미 이동한 경로를 가져온다.
});
여기에서 잠시 주목할 것은 popstate는 가장 마지막에 pushState 상태를 가져오는데 최초 페이지 로딩 후 History 객체를 보면
> history
  length: 1
  state: null
}
이와 같이 length는 1이고 state가 null인것을 볼 수 있다.
우리는 원하는 곳으로 보내야하기 때문에 이 부분을 바꿔줘야한다.
미리 pushState를 해놓으면 불필요한 history가 생기므로 같은 문법이지만 history를 생성하지 않는 replaceState( <state>, "<title>", "<url>"); 로 대치해준다.

요약하면 이렇다.

  1. 최초 진입시 URL과 상태를 history.replaceState 로 초기화한다.
  2. 화면 이동시 history.pushState 로 이력을 저장한다.
  3. back/forward 시 popstate 이벤트를 받아서 화면 이동을 구현한다.
여기까지는 사실 쉽다.
나머지는 URL Parsing 부분인데
이쪽에 구현해 놓았다.

가상 경로명을 Session에 넣어서 다루었다.

router.routes = {
  "home": /^\/$/,
  "view": /^\/view\/([^/]*)$/,
};
라우터는 이렇게 만들어 놓고 location.pathname 을 정규식으로 매칭해서 같은 패턴이면 template이름을 key로 사용하게 하였다.
만일 매치에 실패하면 router.notfound 를 사용하도록 하고 정의해주도록 하면 좋다.

Template 구조는 이렇게 구성했다.
홈에서 두 개의 하위 링크로 이동을 구성하는 아주 단순한 구조로

초기화면
Home
A
B
View
A
home

이렇게 진행한다.
라우터를 담당할 템플릿에서 어떤 템플릿을 사용할지는 동적으로 결정해야하므로
{{> UI.dynamic template=getRoute}}
를 사용한다.
getRoute에 표현할 template이름을 helper로 넣으면 된다.
실제로 경로를 이동하는 이벤트가 발생하는 곳에서 preventDefault로 이동을 막고 Session.set('pathname', location.pathname') 하고 pushState를 하면 끝.




*. Meteor 에서 Subscribe 연동 (2회차로?) (https://www.discovermeteor.com/blog/template-level-subscriptions/)

Template 별 subscribe 관리는
onCreated 에  
  this.subscribe("postsView", Session.get("params"));

로 지정하면 onDestoryed 시점에 알아서 정리한다.

Template 안에서는 subscribe완료 여부는

{{#if Template.subscriptionsReady}}
{{/if}}

이렇게 체크 가능.
helpers나 events에서Template.instance().subscriptionsReady() 를 사용하면 Reactive로 사용할 수 있어서 완전 꿀.