1

React+Fluxxor+socket.ioでfluxなチャットを作った

fluxxorとsocket.ioを使ってチャットを作った。大変ミニマムな感じの実装を心がけてて、今後何かWebアプリを作る時はこれを少し組み替えて作るぞというのを意識してる。ようするに低機能最低限でしょぼい。サーバー側はexpressとmongodb。

https://github.com/shokai/node-flux-boilerplate

herokuでも動く
https://node-flux-boilerplate.herokuapp.com/

クライアントはこんな実装


Fluxアーキテクチャ

Reactはなんとなくわかってfluxをやるか、と思ったらflux npmの中がdispatcherしかなかった。
flux.DispatcherのwaitForの実装


まずこのへんを読んでフムフムなるほど(わかったようなわからないような)となる。
Flux | Application Architecture for Building User Interfaces


で、このスライド見たらわかりやすすぎて気絶した。とりあえずなんらかのfluxフレームワーク使ってチャットでも作る事にした。


あとこっちも、フレームワークなしで最低限のfluxを自作するとしたらこんな感じになるのね、とわかって気が楽になる。
これ読んでおけば、Fluxxor等のフレームワークがどういう工夫をしているか差分を理解しやすくなりそう。
10分で実装するFlux



Fluxxorというフレームワークを使う



http://fluxxor.com/


FluxxorはFluxなフレームワークで、StoreとActionとDispatcherを担当する。ReactをViewとして使うこと前提に作られている。

この記事がとてもわかりやすい
Fluxなフレームワーク、Fluxxorの紹介 – マルシテイアは月の上


で、Fluxxor+ReactにSocket.ioを追加してチャットをつくってみて、こんな感じの実装になった。


fluxはaction→dispatcher→store→view→actionの4つの部品で構成されるが、fluxxorは三叉のロゴのとおりDispatcherが無くてaction/store/viewだけになる。

Dispatcherはfluxxorを使うことでactionとstoreに内蔵されるようになっている。そしてactionからはdispatch関数のみが、storeからはbindActionでの受信のみがアクセスできる様になっているので、action→storeの1方向にしかメッセージが流れない。試してないけどwaitForで順序制御もできるようだ。

fluxorのstoreにはeventemitterが継承されていて、emit(“change”)するとViewにイベントが飛ぶ。ViewはcreateClassした時のmixinsにFluxxor.storeWatchMixin(Store名)を指定すると、changeイベントを受けたらrenderを呼ぶようになっている。ここでも1方向のメッセージングが強制される。

Viewからactionを呼び出すのはthis.getFlux().actionsの下に関数が生えているので、それを呼び出す。

ふつうにfluxを自分で作ったら、dispatcherがどこからでも見える場所にあったりとかで、1方向にしかメッセージを流さないことを心がける事はできても、メッセージの逆流を防ぐ実装をするのはちょっと面倒臭い。fluxxorに乗ってやると円環の理が強制的に生まれる。


socket.ioどこに置くか問題


socket.ioからの入出力はactionを通してfluxとやりとりする。

受信
socket.ioからチャットメッセージ受信→action→storeのlogsに追加→reactがView更新

送信
チャット送信ボタン押す→action→socket.ioでサーバーに送信

socket.io関係のコードをどこに置くかでけっこう迷った。
最初はChatStoreに持たせようとして、でもただの「接続しました!」みたいなStore関係ないメッセージ表示するようなコードはどこに置くのか。

結局fluxの外に独立した部品として設置してactionで通信することにしたけど、送信時のactionからのdispatchを受信させる方法が良くない。dispatchはstoreのbindAction以外にも、flux.on “dispatch”, (type, payload) ->でも受信できるけど、

An action of type send-chat was dispatched, but no store handled it dispatcher.js:105


というwarningがどうしても出される。これを避けるにはどうしてもStoreのフリをするしかないし、そもそもdispatchがstoreじゃない所に飛ぶ可能性を作るのはせっかくfluxとかいう円環の理を作ってバグの場所を特定しやすくした意味がぶち壊しになるのでやめた。
おとなしくactionから関数を呼び出すことにした。socktes/という別ディレクトリを掘ってその中にsocket.io関連のコードはまとめて、fluxとのやりとりは必ずaction経由でやる。

0

WEB+DB Press vol.86でAndroid Wearについて書いた

数日前発売のWeb+DB Press vol.86でAndroid Wearについて書いた。

Moto360

Android Wearは去年秋から半年ぐらい使っていて、特に面白いと思った機能が「通知」だったので、今回は通知するアプリを作った。
歩行中などふだんスマホを見ないタイミングでもAndroidはけっこう通知をしていて、例えばGoogle Oneの機能では直前に検索した施設へ行けるバス停が近くにある時に時刻表が出ていたりとか、FireChatが「周囲にFireChatを使っている人がいます」とか、場所や状況に応じて通知してくる。でもスマホだけでは(外を歩いているから)バイブレーションでも通知音でも気がつかない。
Wearつけてると腕が振動して、しかも時計を見るジェスチャだけで確認できる。

ちょうど発売されたばかりのApple Watchもそんな感じなんじゃないかと思う。Apple Watchの実物見たこと無いけど。


通知

というわけで外を歩いてる時に通知してくれるもの実装するか〜〜となって、Google MapsとGeofenceから場所のリマインダを作れるアプリを作った。

https://github.com/shokai/Android-GMapStars

ラーメン屋とか調べたら忘れないようにGoogle Mapsで星つけておくんだけど、たまたま別の用事で近くを通りかかった時にニアミスしたまま通り過ぎていたりする事がけっこうある。星つけた後、Mapの共有メニューからGeofenceを登録できるようにして、後日近くに行った時にWearに通知がでて、そこからMapを開けるアプリを作る話を書いた。

なおWearアプリはWearにアプリを書き込んでWear上で実行するのと、スマホ本体の通知をWear用にカスタムして表示するものの2種類があって、今回は後者でやっている。


WEB+DB PRESS Vol.86
WEB+DB PRESS Vol.86
posted with amazlet at 15.04.23
結城 洋志 沖元 謙治 足永 拓郎 林 健太郎 大竹 智也 内田 誠悟 伊藤 直也 中山 裕司 hiroki.o 泉水 翔吾 佐藤 太一 高橋 俊幸 西尾 泰和 舘野 祐一 中島 聡 橋本 翔 はまちや2 竹原 麻植 泰輔
技術評論社
売り上げランキング: 402


1番ライフチェンジングなのは通知で、2番は音声入力だと思う。音声入力で実世界系の事しようとするとさらに別のガジェットが必要になるので見送った。個人的にはWearから音声入力でツイートできるtwitterクライアント作ってる。音声入力からの画像検索(ok google 「hogehoge 画像」でできる)も、ド忘れした物事を一瞬で調べれて脳が拡張されてる感じある。


あとWearの写真撮るとどうしても腕毛が気持ち悪いので剃ったりとか色々してみたけど、結局毛穴も気持ち悪いのであきらめた。

Moto360

0

flux.DispatcherのwaitForの実装

Promiseもコールバックも使ってないのにDispatcherのwaitForで順番の制御ができる。どういう事なのか気になったので調べた。


flux.Dispatcher

Reactはなんとなく使えるようになった気がしたので、fluxでやろう、StoreとかActionとかどう書くんだと調べてて、とりあえずflux npmというFacebookの中の人によるfluxアーキテクチャの説明とDispatcherが一つだけ入っているnpmを見ていた。

Dispatcherは使ってみた感じ、イベント名が無いEventEmitterみたいな感じで、登録順に実行される。

flux = require 'flux'
Dispatcher = new flux.Dispatcher

Dispatcher.register (action) ->
console.log "1 - #{JSON.stringify action}"

Dispatcher.register (action) ->
console.log "2 - #{JSON.stringify action}"

Dispatcher.register (action) ->
console.log "3 - #{JSON.stringify action}"

Dispatcher.dispatch
actionType: 'add-food'
food: 'beef'

Dispatcher.dispatch
foo: 'bar'
1 - {"actionType":"add-food","food":"beef"}
2 - {"actionType":"add-food","food":"beef"}
3 - {"actionType":"add-food","food":"beef"}
1 - {"foo":"bar"}
2 - {"foo":"bar"}
3 - {"foo":"bar"}


waitFor([id…])


なんかwaitForにregisterメソッドから返ってくるIDを渡すと、そのコールバック呼び出しの順序を制御できるらしい。便利そう

flux = require 'flux'
Dispatcher = new flux.Dispatcher

wait_ids = []

Dispatcher.register (action) ->
Dispatcher.waitFor wait_ids
console.log "1 - #{JSON.stringify action}"

wait_ids.push Dispatcher.register (action) ->
console.log "2 - #{JSON.stringify action}"

Dispatcher.register (action) ->
console.log "3 - #{JSON.stringify action}"

Dispatcher.dispatch
actionType: 'add-food'
food: 'beef'

2 - {"actionType":"add-food","food":"beef"}
1 - {"actionType":"add-food","food":"beef"}
3 - {"actionType":"add-food","food":"beef"}

何度もwaitFor

1つのコールバック内でwaitFor→なんか処理→またwaitFor→なんか処理 ができる。気持ち悪い。
flux = require 'flux'
Dispatcher = new flux.Dispatcher

wait_ids = []
wait_ids2 = []

Dispatcher.register (action) ->
Dispatcher.waitFor wait_ids
console.log "1 - #{JSON.stringify action}"
Dispatcher.waitFor wait_ids2
console.log "done!"

wait_ids.push Dispatcher.register (action) ->
console.log "2 - #{JSON.stringify action}"

wait_ids2.push Dispatcher.register (action) ->
console.log "3 - #{JSON.stringify action}"

console.log "wait_ids #{wait_ids}"
console.log "wait_ids2 #{wait_ids2}"

Dispatcher.dispatch
actionType: 'add-food'
food: 'beef'

wait_ids  ID_2
wait_ids2 ID_3
2 - {"actionType":"add-food","food":"beef"}
1 - {"actionType":"add-food","food":"beef"}
3 - {"actionType":"add-food","food":"beef"}
done!


waitForの実装

https://github.com/facebook/flux/blob/2.0.2/src/Dispatcher.js#L154-L177

waitForというより、指定idのコールバックを先に実行しているだけ。待っているように見えてるだけだった。

そもそもDispatcher.registerにはEventEmitterのようにイベント名は登録できないので、dispatchしたらregister済みの全てのコールバック関数は登録順に即実行される。(actionTypeなどのプロパティで自分宛てなのか判定してねという作法になってる)

dispatchすると、コールバックは実行直前に自分のidにisDispatchingフラグを立てる。
コールバック内でwaitFor([id…])を呼ぶと、id指定したコールバックをその場で実行しようとする。
対象がisDispatchingの場合は次のidを見る。
waitForで指定されたidのコールバックの中で、さらにwaitForが呼ばれたら再帰的にまた同じことをやる。

自分のidにはpendingフラグが立っていて、pendingなコールバック間でwaitForがループするとCircular dependency detected while…というエラーがthrowされる。

waitFor指定したコールバックの呼び出しが完了したら、そのまま処理が続くので順番が制御できる。

0

WEB+DB PressのReactの記事が良かったのでcoffeeで書きなおした

昨日か今日ぐらいの発売のWEB+DB Press vol.86のp123~のnaoya氏のReactの記事が良いらしいからみんなで勉強しましょう、って増井先生が言ってたので、昨日夕方数人で読んでて家帰ってから実際にコード書いてみた。

JavaScript苦手なのでcoffee-scriptで写経した。

https://github.com/shokai/react-coffee-study

observer modelかつデータの流れを1方向にするのをFluxアーキテクチャと呼ぶのとか、DOM切り貼り職人のつらみとVirtualDOMの差分更新とかそういう概念の話は知ってたけど、なぜかなかなかReactそのものを使う機会が無かった。

Reactの感想

触ってみた感じではBackbone+Marionetteよりとっつきやすい。
WEB+DB Pressの記事の前半のtodoリストはみたままcoffeeで書いて、それですぐ馴染んできたので後半のmarkdown previewはなんとなく適当に書いたらちゃんと動いた。



coffeeでreact(coffee内にJSX)

最終的にcjsxというcoffee内にJSXを書ける拡張を使って、
Gruntからbrowserify+coffee-reactifyでsourceMap付きでJSX変換済みのjsを吐き出すようにした。
NODE_ENV=production付けるとさらにuglifyjsで圧縮する。
リポジトリ内のGruntfile.coffee見るとわかる。


ReactにはJSXというHTMLのようなテンプレートエンジンが埋め込まれたJavaScriptが使われていて、ふつうのJSの中にこういうのを書くと

こういうJSに変換される。

つまりHTMLっぽいJSXというマークアップを書く→JSに変換→VirtualDOMが構築され→適当なタイミングで本物DOMが差分更新される、らしい。

このJS内のJSXの変換は、WEB+DB Pressの記事では最初JSXTransformer.jsでクライアントサイドでやらせてて、後半はbrowserifyでサーバー側であらかじめ変換する方法を取っていた。


coffeeでjsx使うにはcoffee-react npmの中にあるcjsxコマンドを使うんだけど、この後にさらにbrowserify通すとsourcemapが崩れてデバッグ不能になる。
多段階の変換するとsourcemapズレたりどっか飛んでいってしまってよくないので、cjsxの変換もbrowserifyにやらせる。


browserify

ReactはjQueryと共存できない→jQueryないと俺はajaxできない→ajax機能だけのjQueryをカスタムビルドするのもまあアリかもしれないけど、ここはbrowserifyでnodeのライブラリをブラウザに持ってくれば解決、あとeventemitterとかも持ってこれるし便利!

というわけでbrowserifyというJSを静的解析してrequire先のファイルを全部1つのjsファイルに固めてくれるツールを使う。

debugオプション付ければsourceMapもちゃんと付くので、cjsxでのエラーもその先のnpmのファイル名や行番号も見える。

全部リポジトリ内のGruntfile.coffeeに書いた。


0

HubotでYoのwebhookを受信する

Yoは最近はダッシュボードからbotアカウントを作れるようになっていて、俺もいくつか作って使っている。

botがYoを受信するとあらかじめ指定しておいたコールバックURLにHTTP-GETでwebhookしてくれる。

https://自分のhubot.herokuapp.com/yo/webhook?token=kazudon とか指定しておくといい。GETのパラメータでusername, user_ip, tokenが来る。


Hubotで受信するとしたらこんな感じ。Hubotにはexpressが内蔵されていてrobot.routerで使える。
環境変数YO_WEBHOOK_TOKENを設定する必要がある。

本当にYoのサーバーからのリクエストかを確かめるのにコールバックURLにtokenを含めるのと、HTTPSを使う。
誰でもYoからプログラム実行できても困るので、許可するYoユーザーをあらかじめ持っておく。

# Description:
# YoのWebhookを受信して何かする
#
# Author:
# @shokai

module.exports = (robot) ->

unless process.env.YO_WEBHOOK_TOKEN
robot.logger.error 'ENV var "YO_WEBHOOK_TOKEN" is missing'
return

yo_users = ['SHOKAI', 'user1', 'user2'] # Yo許可するユーザー

robot.router.get '/yo/webhook', (req, res) ->
ip = req.query.user_ip

unless who = req.query.username
return res.status(400).end 'invalid request'
if yo_users.indexOf(who) < 0
res.status(400).end "bad user: #{who}"
robot.send {room: "#general"}, "不正なユーザー #{who}(#{ip})がYoしてきました"
return
if req.query.token isnt process.env.YO_WEBHOOK_TOKEN
res.status(400).end "bad token"
robot.send {room: "#general"}, "#{who}(#{ip})が不正なtoken#{req.query.token}でYoしてきました"
return

res.end 'ok'
robot.send {room: "#general"}, "#{who}(#{ip})がYoしてきました"
## 〜〜ここで何か処理〜〜 ##