24
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

VirtualDOMAdvent Calendar 2014

Day 18

Reactでハムスター用回し車を作ったから見てくれ頼む

Posted at

動機

なんでつくったの

  • 仕事でcss3のtransformをちょっと使ったが、もっと遊べそうだと思った
  • React.js もっといじってみたかった

なんで公開したの

  • 身の回りに React 使ってる人がいない
  • っていうかそもそも JavaScript 使い込んでる人がいない
  • 自分のやってることがいいのかゴミなのか見当がつかんから見てくれ
  • あと、Reactつかってドラッグ扱ったが、fluxしたら幸せな気分になったから聞いてくれ
  • VirtualDOM Advent Calendar? おもしろそうじゃないか

サンプル

ピンク色のデカい回し車があるので、回してみてください。(下向きにドラッグすると回ります)
おもいっきりどうぞ。

なんでデカいか? そうじゃない。あなたが小さいんだ。
**あなたはハムスターだ。**いいね? 回すんだ。

一応回した回数とかも計測しておきました。

リポジトリ

実装解説

回し車の"3Dモデル"はsrc/js/app/hamwheel.js

"3Dモデル"っつっても<div>でつくった"ポリゴン"を貼りあわせてるだけだし、三角関数とCSSを生成してるだけで、今回は話題も違うしスタイルシート(transform)の説明はまるっきり省略する。(perspectivetransform:perspective()の関係にも苦労したので、その話は別に書くかもしれない)

HamWheelコンポーネントでやりたいことはあんまり多くなくて、というか結局1つしかなくて、それは「ドラッグしたらくるくる回せる」ってことだ。

ドラッグ

今回はドラッグを、古式ゆかしい「mousedownmousemovemouseup」方式で実装した。
Reactにもドラッグイベントは存在することになっているが、あれはdraggableなオブジェクトを別の場所にドロップするためのやつで、ただマウスの移動距離が取れればいいなら却って面倒くさい。もっとコンパクトなモデルで試した時にはうまく「ドラッグ中」にならなかった。

ピクセルと角度の変換比(degreeParPx)をpropsから受け取ることにしておいて、マウスの移動距離を測り、イベントとして上に投げればよろしい。その辺の実装はhandleMouse(Down|Up|Move)に書いてある。

回る

propsなりstateなりで回転角を受け取って、モデルの描画時にこれを角オフセットに与えれば良い。
なお、回転角のことをangulerと呼んでおり、随所にこの語が出てくるがAngulerJSは今回一切関係ない。

ドラッグ「したら」回る

onmousemove/handleMouseMoveのたびにsetStateなんかすると、猛烈な勢いで仮想DOMのパッチが当たり続けることになる。
この手の処理は常に更新され続ける"結果"をどっかに書き込んでおいて「適当なインターバルで」その書かれた値を元に画面を更新するのが定石であるようだ。
さて、一体どこに放り込んでおいたらいいんだろう?

......fluxにおいては、どっかに書き込んでおくといえばそれはストアのことだ。
ここに迷いはない。
その辺のグローバル変数に放り込んでおくこともないし、なんか命名規約とか作る必要もない。
クロージャめいたことも、新しい機構を探してきたり作ったりすることも、必要ない。
ごくごく順当にfluxするだけでいいのだ。
これはハッピーなことなんじゃないか?

そういう思想で、このへんのアニメーション処理の旗振りはストアにまかせてしまうことにした。
ストアはsrc/store/anguler_store.jsAngulerStorage#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`という静的メソッドが生やしてある。
結局のところ上記メソッドのラッパなのだけど、多少はマシな気分になる。

RgbLoop

色の操作と繰り返し操作を素で書きたくなかったから書いた。それだけ。

24
22
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
24
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?