動機
なんでつくったの
- 仕事でcss3の
transform
をちょっと使ったが、もっと遊べそうだと思った - React.js もっといじってみたかった
なんで公開したの
- 身の回りに React 使ってる人がいない
- っていうかそもそも JavaScript 使い込んでる人がいない
- 自分のやってることがいいのかゴミなのか見当がつかんから見てくれ
- あと、Reactつかってドラッグ扱ったが、fluxしたら幸せな気分になったから聞いてくれ
- VirtualDOM Advent Calendar? おもしろそうじゃないか
サンプル
ピンク色のデカい回し車があるので、回してみてください。(下向きにドラッグすると回ります)
おもいっきりどうぞ。
なんでデカいか? そうじゃない。あなたが小さいんだ。
**あなたはハムスターだ。**いいね? 回すんだ。
一応回した回数とかも計測しておきました。
リポジトリ
実装解説
回し車の"3Dモデル"はsrc/js/app/hamwheel.js
。
"3Dモデル"っつっても<div>
でつくった"ポリゴン"を貼りあわせてるだけだし、三角関数とCSSを生成してるだけで、今回は話題も違うしスタイルシート(transform
)の説明はまるっきり省略する。(perspective
とtransform:perspective()
の関係にも苦労したので、その話は別に書くかもしれない)
HamWheel
コンポーネントでやりたいことはあんまり多くなくて、というか結局1つしかなくて、それは「ドラッグしたらくるくる回せる」ってことだ。
ドラッグ
今回はドラッグを、古式ゆかしい「mousedown
→mousemove
→mouseup
」方式で実装した。
Reactにもドラッグイベントは存在することになっているが、あれはdraggable
なオブジェクトを別の場所にドロップするためのやつで、ただマウスの移動距離が取れればいいなら却って面倒くさい。もっとコンパクトなモデルで試した時にはうまく「ドラッグ中」にならなかった。
ピクセルと角度の変換比(degreeParPx
)をprops
から受け取ることにしておいて、マウスの移動距離を測り、イベントとして上に投げればよろしい。その辺の実装はhandleMouse(Down|Up|Move)
に書いてある。
回る
props
なりstate
なりで回転角を受け取って、モデルの描画時にこれを角オフセットに与えれば良い。
なお、回転角のことをanguler
と呼んでおり、随所にこの語が出てくるがAngulerJS
は今回一切関係ない。
ドラッグ「したら」回る
onmousemove
/handleMouseMove
のたびにsetState
なんかすると、猛烈な勢いで仮想DOMのパッチが当たり続けることになる。
この手の処理は常に更新され続ける"結果"をどっかに書き込んでおいて「適当なインターバルで」その書かれた値を元に画面を更新するのが定石であるようだ。
さて、一体どこに放り込んでおいたらいいんだろう?
......fluxにおいては、どっかに書き込んでおくといえばそれはストアのことだ。
ここに迷いはない。
その辺のグローバル変数に放り込んでおくこともないし、なんか命名規約とか作る必要もない。
クロージャめいたことも、新しい機構を探してきたり作ったりすることも、必要ない。
ごくごく順当にfluxするだけでいいのだ。
これはハッピーなことなんじゃないか?
そういう思想で、このへんのアニメーション処理の旗振りはストアにまかせてしまうことにした。
ストアはsrc/store/anguler_store.js
。AngulerStorage#add
すると回転角の追加分がストアに登録される。
回転の進捗を画面に出そうとしたせいでごちゃごちゃしてるが、結局のところstorage.anguler
が現在の回転角になる。
AngulerStore#startInterval
するとインターバルタイマが走り始めて、定期的にその瞬間の回転角を教えてくれるようになる。stopInterval
でまた黙る。
今回はこのタイマを普通にsetInterval
で組み込んだが、requestAnimationFrame
でもなんでも別に好きなものを使えばよろしい。
そういったものをReactが直接サポートする必要が特に無いことがわかると思う。
ここまで来たらあとはドラッグ時のイベントをひろって適当なディスパッチャ経由でストアにつなぎこめば良い。(src/html/index.html
)
うっかりすると面倒なことになるデータフローも、普通にfluxすればよかったんだ!
動いた
いやーよかったねえ。
コンポーネントは「自分がいつ更新するべきか」についての関心を綺麗さっぱり手放して「自分はどういう画面になるべきか」に注力することができた。
componentShouldUpdate
つかって細かく制御いれていく方法もあったのかもしれないが、一度こうやって書いてしまうとそんなことをする理由はないように思われる。
ストアに角を保存したということは、画面を再読み込みしたときには元の角が再現可能だということまで含意してしまうと思う。(そんな機能は今回積んでないけど)
これがよいことなのかどうかはまだわからん。
その他。
- 結局この設計がましなものなのか、世の人が見れば地獄に見えるのかがわからない
- 期せずして闇を産んだ可能性がある
- 生jsが闇というのはわかる
- jsxを書く気はしなかった
-
render
が長すぎる気はしてる。スタイルをいじりすぎた
- 何がストアに置かれるべきか
- たとえば「マウスがホバーしてるあいだだけ震える」みたいな状態は、いちいちストアを経由すべきでない気がする(コンポーネントの
state
に閉じていて良い) - 回し車の回転角はストアにおいたから実績を表示できた(結果的にコンポーネントに閉じていなくてよかった)
- ストアに置かれないものは、コンポーネントの外(他のコンポーネントとか、サーバの奥のDBとか)に伝播する余地がない
- たとえば「マウスがホバーしてるあいだだけ震える」みたいな状態は、いちいちストアを経由すべきでない気がする(コンポーネントの
- ストアの実装なんとかならなかったの
- Bacon.js みたいなやつは使わなかった
- FRP基盤はストアにできるだろうが、flux的には本質的な問題じゃない
- それはそれとして、実績記録の実装はグロくなってしまった
- ストア/ディスパッチャをうまく書くというのが仮想DOMを使う上での課題になるだろう
fluxの考え方は僕を導いてくれる
- 問答無用で関心が分離される
- 中途半端な状態にしようと思わなくなる
- 変な裏口とか作ろうと思わなくなる
- 光だ
蛇足
本筋以外の実装について。
Component.create
React.createElement(MyComponent, {some : 'of', properties : 42}
, [children])とか書きたくない。 僕の作るコンポーネントには
create`という静的メソッドが生やしてある。
結局のところ上記メソッドのラッパなのだけど、多少はマシな気分になる。
Rgb
とLoop
色の操作と繰り返し操作を素で書きたくなかったから書いた。それだけ。