シンプルでも最小の秩序を持ったオブザーバーパターンが使えるライブラリ Obseriot を書いた

Flux によりもてはやされてる感のあるオブザーバーパターンですが、個人的に Flux はちょっと大袈裟すぎて疲れてしまいます。

もっとシンプルにできないものかと思って Riot.js に組み込まれてる [riot-observable](https://www.npmjs.com/package/riot-observable) をラップしてちょっと便利にした [Obseriot](https://www.npmjs.com/package/obseriot) を作りました。

Riot.js での悩みを解決するために作りましたが、Riot.js( 本体 ) は不要なので何にでも合うはずです。

2016/08/01: [Fluxのように使う方法](#%E3%81%8A%E3%81%BE%E3%81%91-flux-%E3%81%AE%E3%82%88%E3%81%86%E3%81%AB%E4%BD%BF%E3%81%86)を追記しました。

# TL;DR

Obseriot で解決したかったのは以下の点です。

- 使えるイベントに秩序が欲しい。定義したイベントだけを使いたい。
- 受け取れる値にも秩序が欲しい。常に定義したフォーマットで受け取りたい。

そこで `riot.observable()` の `on` と `trigger` をラップして以下のように使えるようにしました。

```js
// 通知可能なイベントをオブジェクトとして定義
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` が呼び出されたときに実行されるコールバックを登録します。

```js
obseriot.listen( test.example, function ( opt ) {
    console.log( opt )
} )
```

## notify

イベントを発行します。第 2 引数は可変長で好きなだけ渡すことができます。

```js
obseriot.notify( test.example, { name: 'test' }, 1, 2, 3 )
```

## remove

リスナーを解除します。

すべてのリスナーを解除するなら第 2 引数は必要ありません。特定のリスナーだけを削除する場合は、第 2 引数にリスナーを示すメソッドを渡します。これは `removeEventListener` に似ていて、無名関数のリスナーは削除することができません。

```js
obseriot.remove( test.example ) // すべてのリスナーを削除
obseriot.remove( test.example, listener ) // ひとつのリスナーを削除
```

## once

1 回だけ実行されるリスナーを登録します。

処理したい内容がひとたび実行されれば不要になることが分かっている場合に役立ちます。

```js
obseriot.once( test.example, function ( opt ) {
    console.log( opt )
} )
```

# インストール

npm からインストールできます。

```
npm install obseriot
```

[npm: obseriot](https://www.npmjs.com/package/obseriot)

# 使いみち

...というほど決まった使いみちはありません。

シンプルでちょっとした秩序を持ったオブザーバーパターンを実現するはずです。

# ソース

[GitHub](https://github.com/obseriot/obseriot) に置いてあります。

# (おまけ) Flux のように使う

Flux が大袈裟とはいえ、データの交通整理というのは求められています。

Obseriot はオブジェクトを購読可能にする仕組みを提供しているので、使い方によっては Flux のように使うこともできます。しかもよりシンプルに。

簡単な例を書いてみます。

## アクションの定義

コンポーネントから呼び出すためのアクションを定義します。

```js:action-increment.js
export const actionIncrement = {
    handler : {
        name : 'action_increment',
        action ( num = 1 ) {
            return [ num ]
        }
    }
}
```

## ストアの定義

アクションによって状態を更新し、新しい状態を通知するためのストアを定義します。

```js:store-count.js
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` を通知します。

```js
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` なら引数無しでも動きます。

ストアの更新を待ち受けている別のコンポーネントでは、以下のように新しい状態を受け取ることができます。

```js
import obseriot from 'obseriot'
import { actionIncrement } from './action-increment'
import { storeCount } from './store-count'

obseriot.listen( storeCount, newCount => {
    console.log( newCount ) // => 1 
} )
```

簡単ですね。