Help us understand the problem. What is going on with this article?

Chaplinでのroutes登録のイベント取り回し関係をまとめました

More than 5 years have passed since last update.

Chaplinjsで渡されるroutesについて、イベントのpub/subの挙動を真面目に読んでいなかったところを読んだのでまとめました。

Chaplin.route.event.png

端的にいうと、イベント(route:match)をpublishするための登録フロー(Router -> Route -> Backbone.history)とsubscribeするための登録フロー(Dispatcher -> Route)があり、監視されているURLの変更(popstateやhashchangeなど)でイベントが発火 -> Controllerのロードとactionの実行、になっています。

Application

Applicationは内部で下記のinitを実行します。
* initDispatcher
* initLayout
* initMediator
* initRouter

publishのフロー

initRouter

initRouterの中ではApplicationに渡されたroutesoptionsを受け取ります

@router = new Router options

として、routerを初期化し、Router#matchというメソッドを受け、渡されたroutesを実行します。

routesは下記のようにpatterntarget(に加え必要であればoptions)を受け、URLの変更に対して適切なControllerのactionを対応付けます。

initRouterの中では

routes? @router.match

が実行され、それぞれのmatch pattern, targetが実行されます。

matchでは、

route = new Route pattern, controller, action, options

として、routeオブジェクトが作成され、その後

Backbone.history.handlers.push {route, callback: route.handler}

としてhistory handlerに登録されます。
最後にrouteが返却されますが、routesの中では特になにも処理されていません。

routesの中で記述したルーティングが処理される機会は、このinitRouterだけになります。

Backbone.history

では、このルーティングはどのように制御されているかというと、すべてはBackbone.historyの中だけで処理されます。

Backbone.historyは、startメソッドが実行されると、hashChangeだけなのか、pushStateを見るのか、などのフラグ判別の処理の末、

      if (this._hasPushState) {
        addEventListener('popstate', this.checkUrl, false);
      } else if (this._wantsHashChange && this._hasHashChange && !this.iframe) {
        addEventListener('hashchange', this.checkUrl, false);
      } else if (this._wantsHashChange) {
        this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
      }

というように、

  1. popstate
  2. hashchange
  3. setInterval の3つの方法でURL変化の監視が開始されます。

URLの変化に対してhistory.checkUrlが実行されます。
この中で、setIntervalに対応するために実際にURLが変化しているかどうかのチェックが行われ、通過すると、history.loadUrlが実行されます。

Backbone.history.loadUrl

ここでようやく、Backbone.history.handlersに登録しておいたrouteが評価されます。

      return _.any(this.handlers, function(handler) {
        if (handler.route.test(fragment)) {
          handler.callback(fragment);
          return true;
        }
      });

handlersの中から、現在のfragment(ざっくりURL)にマッチするものが存在するかがRoute#testでチェックされ、マッチしたcallbackが実行される、という挙動になります。

ここでのcallbackは、前述のとおり、Chaplin.RouterBackbone.history.handlers.push {route, callback: route.handler}として登録されています。

Chaplin.Route#handlerでのイベントpublish

さて、Route#handlerはどうなっているでしょうか?

例えば /users/33/posts/123?fields=comments のような fragment が渡されると、query も含めてパースされ最終的には

routes = {path, @action, @controller, @name, query}

というオブジェクトをつけて

    @publishEvent 'router:match', route, actionParams, options

として、イベントが発行されます。

subscribeのフロー

Chaplin.Dispatcher

Chaplin.Dispatcherは初期化時にroute:matchイベントを@dispatchで待ち受けます。

もろもろのチェックが通ると、Dispatcher#loadController(name, handler)が発火します。
loadControllerでは、ついに動的なcontrollerのロードが行われます。
Applicationの初期化時にoptionsで渡したcontrollerSuffixcontrollerPathが加味され、controllerのファイルパスが作成されます。

ここでのhandlerは

(Controller) =>
      @controllerLoaded route, params, options, Controller

となっており、ロードされたコントローラに対して、ルーティングの結果得られたパラメタなどが渡されます。

loadControllerの中では、作成されたファイルパスに対して、define.amdまたはrequireでcontrollerモジュールが呼び出されhandler(というかcontrollerLoaded)が実行されます。

Dispatcher#controllerLoadedとDispatcher#executeAction

その後は、controllerLoadedでControllerのインスタンスが作成され、executeActionに引き渡され、その中でactionが実行される事になります。

そこで、ようやく各種のControllerで定義したメソッドのところに届くことになります

class SomeController extends Chaplin.Controller
  someAction: (params, routes, options)->

まとめ

長くなりましたが、順序としては

  1. Applicationの初期化
  2. subscribe処理
    1. route:matchのsubscribe
    2. イベント受け取り時のroute, params, optionsを元に、動的にcontrollerのロード/インスタンス化/Controller#Actionの実行
  3. publish処理
    1. Application#initRouter
    2. routes(Router#match)により、Backbone.history.handlersへの追加
    3. Backbone.history.startによるURL変化の監視
    4. handlers内のmatchするhandlerのcallbackの実行
    5. Route#handlerでのroute:matchイベントのpublish

となっていることを読み解きました。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした