LoginSignup
6
7

More than 5 years have passed since last update.

knockout.jsでPub/Subする

Last updated at Posted at 2017-04-01

課題

knockout.jsのコンポーネント間のやり取りで、互いのViewModelを参照していたりと密結合になっていてリファクタリングがしづらい状況になっていた。

たとえば、コンポーネントAの処理で、コンボーネントBの値が変わってほしい場合に、コンポーネントAのViewModelにコンポーネントBのViewModelのインスタンスを取得するメソッドを作ったりしていた。

こんな感じ。

coffeescript
class ViewModelA
  clickButton: ->
    @getViewModelB().name("new_value")

  getViewModelB: ->
    window.view_models.view_model_b

class ViewModelB
  constructor: ->
    @name = ko.observable()

$ ->
  window.view_models = {
    view_model_a: new ViewModelA(),
    view_model_b: new ViewModelB()
  }

こうなると、Bのインスタンスがある前提で処理を書かなければならず、Bがない画面ときはエラーになるのでBの存在チェックを行う必要性が出たりなど、だんだん複雑化していった。

coffeescript
class ViewModelA
  clickButton: ->
    vm = @getViewModelB()
    if vm? # Bがない場合を考慮
      vm.name("new_value")

  getViewModelB: ->
    window.view_models.view_model_b

ViewModel同士が少なければこれでも耐えられたのだけれど、だんだん苦しくなってきた。

調査

AndroidのIntentのように、とりあえず投げるからそれを拾って勝手に処理してくれる仕組みがほしいなぁと考えるようになった。とはいえ、こういう課題はよくあることだろうと思ったので、ググってみると、ko.subscribableを使えという記事があった。

毎度毎度忘れてしまうのだが、subscribeは”購読”である。
subscribeメソッドで購読する。通知が届いた際の処理を関数で記述。
notifySubscribersメソッドで購読者に通知を送る。

シンプルな例だと、こういう感じ。

coffeescript
# 購読通知管理用のインスタンスを生成
shouter = new ko.subscribable()

class ViewModelA
  clickButton: ->
    # 通知ID"subscriber_id"に対して"new_value"を送る通知
    shouter.notifySubscribers("new_value", "subscriber_id")

class ViewModelB
  constructor: ->
    @name = ko.observable()

    # 通知ID"subscriber_id"を購読。届いた値をnameに入れる
    # ViewModelAからの通知では、"new_value"が入る
    shouter.subscribe (value) ->
      this.name(value)
    , this, "subscriber_id"

素晴らしい点として、AはBに依存しなくなった。Aはただ通知を送っただけ。
その先に、Bが処理を実行するかどうはAは知る由もない。

Bは、通知が届いたらnameを更新するというようにしているのみ。
それがAからの通知であるとかも意識しなくていい。ただ、通知IDが一致していればいいのである。

Pub/Sub

そして、こういうモデルのことをPub/Subメッセージングモデルということを知った。

Pub=Publish(発行)
Sub=Subscribe(購読)

まさに、Aが発行、Bが購読をしている。

ko.subscribableの課題

ko.subscribableのインスタンスはいろいろなところで使おうと思うとグローバル変数として管理しなくてはいけなくなるので、あまり使いたくない…。また、ちょっとコードが読みにくくなるなぁという印象があった。

ライブラリknockout-postboxを使うと、そのあたりが綺麗に書けるという話だったので使ってみた。

knockout-postboxを使ってみる

導入

knockout-postboxreleasesから、バージョン0.5.2をダウンロードして利用した。

利用

knockout-postboxを使うと、notifySubscribersがpublishになり、わかりやすい。

coffeescript
class ViewModelA
  clickButton: ->
    # 通知ID"subscriber_id"に対して"new_value"を送る通知
    ko.postbox.publish("subscriber_id", "new_value")

class ViewModelB
  constructor: ->
    @name = ko.observable()

    # 通知ID"subscriber_id"を購読。届いた値をnameに入れる
    # ViewModelAからの通知では、"new_value"が入る
    self = this
    ko.postbox.subscribe "subscriber_id", (value) ->
      self.name(value)

また、observableな変数自体に、Pub/Subを設定することもできる。

coffeescript
class ViewModelA
  clickButton: ->
    # 通知ID"change_name"に対して"new_value"を送る通知
    ko.postbox.publish("change_name", "new_value")

class ViewModelB
  constructor: ->
    # 通知ID"change_name"を購読する
    @name = ko.observable().subscribeTo("change_name")
    # @ageの値が変更されたら、通知ID"change_age"に通知する
    @age = ko.observable().publishOn("change_age")
    # 通知ID"sync_email"を購読する他のobservableと同期を取る
    @email = ko.observable().syncWith("sync_email")

まとめ

Pub/Subを採用することで、コンポーネント同士が疎結合になり、かつ、コンポーネントを管理するための親ViewModelみたいなものも必要なくなり、コードが非常にすっきりする。変更にも強くなるので、今のところ悪い点が見当たらない。

コンポーネント連携は全てこの方式に変えていこうと思う。

参考情報

6
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
7