Flux によりもてはやされてる感のあるオブザーバーパターンですが、個人的に Flux はちょっと大袈裟すぎて疲れてしまいます。
もっとシンプルにできないものかと思って Riot.js に組み込まれてる riot-observable をラップしてちょっと便利にした Obseriot を作りました。
Riot.js での悩みを解決するために作りましたが、Riot.js( 本体 ) は不要なので何にでも合うはずです。
2016/08/01: Fluxのように使う方法を追記しました。
TL;DR
Obseriot で解決したかったのは以下の点です。
- 使えるイベントに秩序が欲しい。定義したイベントだけを使いたい。
- 受け取れる値にも秩序が欲しい。常に定義したフォーマットで受け取りたい。
そこで riot.observable()
の on
と trigger
をラップして以下のように使えるようにしました。
// 通知可能なイベントをオブジェクトとして定義
var test = {
example : {
handler : {
name : 'test',
action : function ( opt, a1 = 1, a2 = 2, a3 = 3 ) {
let option = Object.assign( {
name : ''
}, opt )
return [ option, a1, a2, a3 ]
}
}
}
}
// test.exampleをリッスン
obseriot.listen( test.example, function ( opt, a1, a2, a3 ) {
console.log( opt, a1, a2, a3 )
// => Object { name: "test" }, 1, 2, 3
} )
// test.exampleを通知
obseriot.notify( test.example, { name : 'test' } )
事前に handler.name
と handler.action
を含んだオブジェクトを定義します。
obseriot.notify()
を実行すると handler.action
の処理を経て handler.name
がトリガされます。handler.action
が返す値は配列に限らず、文字列でもオブジェクトでも関数でもなんでも使えます。配列にすれば可変長引数として obseriot.listen()
に渡ります。
handler.action
によって受け取り側が受け取りたいフォーマットに整形したり、それ以外の好きな処理も入れられます。
処理が終わると obseriot.listen()
のコールバックが呼ばれます。
API
Obseriot では以下の API を提供しています。
いずれも、第 1 引数には handler.name
と handler.action
を含んだオブジェクトを取ります。
listen
notify
が呼び出されたときに実行されるコールバックを登録します。
obseriot.listen( test.example, function ( opt ) {
console.log( opt )
} )
notify
イベントを発行します。第 2 引数は可変長で好きなだけ渡すことができます。
obseriot.notify( test.example, { name: 'test' }, 1, 2, 3 )
remove
リスナーを解除します。
すべてのリスナーを解除するなら第 2 引数は必要ありません。特定のリスナーだけを削除する場合は、第 2 引数にリスナーを示すメソッドを渡します。これは removeEventListener
に似ていて、無名関数のリスナーは削除することができません。
obseriot.remove( test.example ) // すべてのリスナーを削除
obseriot.remove( test.example, listener ) // ひとつのリスナーを削除
once
1 回だけ実行されるリスナーを登録します。
処理したい内容がひとたび実行されれば不要になることが分かっている場合に役立ちます。
obseriot.once( test.example, function ( opt ) {
console.log( opt )
} )
インストール
npm からインストールできます。
npm install obseriot
使いみち
...というほど決まった使いみちはありません。
シンプルでちょっとした秩序を持ったオブザーバーパターンを実現するはずです。
ソース
GitHub に置いてあります。
(おまけ) Flux のように使う
Flux が大袈裟とはいえ、データの交通整理というのは求められています。
Obseriot はオブジェクトを購読可能にする仕組みを提供しているので、使い方によっては Flux のように使うこともできます。しかもよりシンプルに。
簡単な例を書いてみます。
アクションの定義
コンポーネントから呼び出すためのアクションを定義します。
export const actionIncrement = {
handler : {
name : 'action_increment',
action ( num = 1 ) {
return [ num ]
}
}
}
ストアの定義
アクションによって状態を更新し、新しい状態を通知するためのストアを定義します。
import obseriot from 'obseriot'
import { actionIncrement } from './action-increment'
export const storeCount = {
state : 0,
handler : {
name : 'store_count',
action () {
return [ storeCount.state ]
}
}
}
obseriot.listen( actionIncrement, num => {
storeCount.state += num
obseriot.notify( storeCount )
} )
Obseriot は handler.name
と handler.action
を持つオブジェクトならなんでも使えるので、ストアにそれらを追加することでストアが購読可能になります。
コンポーネント
アクションする側のコンポーネントから、actionIncrement
を通知します。
import obseriot from 'obseriot'
import { actionIncrement } from './action-increment'
import { storeCount } from './store-count'
obseriot.notify( actionIncrement, 1 )
console.log( storeCount.state ) // => 1
引数として 1
を渡していますが、値は何でもいいです。先ほど定義したアクションではデフォルトで 1
を使うので、1
なら引数無しでも動きます。
ストアの更新を待ち受けている別のコンポーネントでは、以下のように新しい状態を受け取ることができます。
import obseriot from 'obseriot'
import { actionIncrement } from './action-increment'
import { storeCount } from './store-count'
obseriot.listen( storeCount, newCount => {
console.log( newCount ) // => 1
} )
簡単ですね。