普通のrailsアプリのBackboneにMarionette付けて思った事
以前書いた記事の反省を元にMarionetteに移行した。
思った以上に快適!
大規模になったらMarionette.js使えとか書いているのは嘘で、普通にBackbone使うときは、初めから使うべき。
Backboneで一番恐ろしいのは、各現場/各開発者毎に異なるオレオレ実装。オレオレ実装作るコストに加え、使う人の思わぬバグや学習コストやスイッチングコスト等諸々考えると、特別な理由がない限りMarionetteみたいな既存のframework使うべき。
あんど。データバインディングを提供してくれる、stickitと一緒に使うと、より一層効果的。
めっちゃ、ソースコードの量が減って、ソースの意図が明快になった。悩みも少ない。工数も勿論減る。
ここから、幾つか思った事を、サッカー見ながらお酒飲みながら、ダラダラ書く。※ちなみに、日本vsオランダ戦見てる。
railsアプリでのjs周辺の作りの推移
今作ってるrailsアプリのjs周辺の作りを振り返ると、以下の流れで推移してきた。
ステップ1)jQuery => jQuery + coffee script
coffee script導入でclassが気軽に使えて、初期化処理と共通処理の記述が明快になった。
が、結局、画面毎に分かれたfile内で自由にjsで動き回り、非常に分かり辛い共通化されたextend元のjsがあり、これはこれで非常にスパゲッティなソースを多々生んだ。この辺りの問題は昔のblogで書いた通り。
ステップ2)ステップ1の状態 => jQuery + coffee + Backbone
これも劇的な変化をもたらした。Backboneでjsをmodularに書く事で、今迄書いてきたソースがどれだけ罪深かったかと思った。もう、元のjQueryダラダラソースには戻れない。
だが、Backbone自体は、非常にsimpleで、色々と構成が悩ましく、ちゃんと仕組み策定やルール化をしないと、数々のオレオレ実装を生んだ。
ステップ1と比べると遥かにマシなのだが、まだまだ多くの課題があった。
この生Backboneに絶望して、他のFWに移った方の気持ちは分かります。生Backboneだけで実際の現場ソース書くのは大変で現実的でない。ただ、次のplugin入れたら大分変わった。
ステップ3)ステップ2の状態 => jQuery + coffee + Backbone + Marionette + StickIt
今がコレ。大分この構成で落ち着いた。バック開発でrails書いてるのと同じ様な感覚で書ける。
基本的な作りがMarionetteに埋め込まれているので、ソースの量とテストの量も減った。
データバインディングもstickitでバッチリ機能してくれる。
とりあえず、これで、そんなに大きな問題は無くなった。この構成での、思った事をかいていく。
どのViewを使うべきか?
生Backboneだけの時は、Viewは1つしか無かったので、それを使えばよいのだが、 Marionette使うと、Viewの選択肢が一気に増えるので、どれを使うべきか考えないといけない。
- 選択肢
- CollectionView / CompositeViewなのか?
- ItemViewなのか?
- Marionette.Viewなのか?
- Backbone.Viewなのか?
例えば、ulタグ + liタグのような親子階層があれば、MarionetteのCollectionViewとItemView使えばいい。
用途がはっきりしてて、Marionetteの想定の範囲内で、ここに悩みは無い。
Viewで悩む時
それ以外の、例えば、erb(railsのview template。jspみたいな物)で既にhtmlは描写している物に対して、幾つかEvent等追加したい時に、どのView使うべきか悩む事になる。
このシチュエーションは、single page appliっぽい作りだとあまり無いのだろうが、普通のアプリに適用する時は頻繁に起こる。
初めは、Backbone.View使えばいいと思ってた。generator-marionetteもBackbone.View出すしね。 template要らないからItemView要らないし、Marionette.Viewは単体で使うメリットが無いように思えた。
Marionette.View!
ところが、色々書いてみて、結局、Marionette.Viewを頻繁に使うようになった。
何が良いかというと、最大の利点は、ui
プロパティ。
このViewで頻繁に使う要素のselectorを定数みたいなimageで登録しておけば、後で、jqueryオブジェクトとして使える仕組みで、本当にちょっとした機能なのだが。
これ書くと大分スッキリする!動きも分かり易い。何か変更ある時も気軽にまとめて変える事が出来る点とても良い。
但し、このまま使うと、this.uiを要素として使えないので、initializeの中に、 bindUIElements
書くのを忘れないように。
※確か、itemViewはtemplateが伴わないとerrが出てたので、ここでの例の用途に合わなかった
composite view
ちなみに、結局、compositeVIewを書く機会はあまり無かった。殆ど、CollectionView + ItemViewで済む。
追記12/22:そう思ってたんだけど、最近ようやくcompositeViewの意義が分かった
http://lxyuma.hatenablog.com/entry/2013/12/03/230652
普通のアプリの初期処理をどこに書くか問題。
初期処理をどこに書くか、結構悩んだ。
これもSinglePageAppliなら、初めのpage決まってるだろうし、そのままApplicationにaddInitializerすれば良い。
普通のアプリと初期化処理
が、普通のアプリで、色んな所から呼び出すのに、globalな初期化書くのはおかしい。この書く場所が個別の画面毎に分かれていてもやっぱり、おかしい。(Applicationの初期化じゃないからね。)
それで、結局、Marionette.AppRouterから呼び出されるController内の関数毎に初期処理を分けた。 まあ、複数の画面毎にやる初期処理全然違うし、もし、railsと同じ感覚なら、controllerのaction毎に処理を分けるのは自然の事だし、これで良い気がする。
※ちなみに、全ての画面に共通するアプリケーション自体の本当の初期化はApplication.setInitializerに入れて良いと思う。ここで話しているのは、個別の初期処理の話。
erbとtemplateどっちが良いか問題
Backbone書くようになって、erb離れてtemplateに生html(+軽いlogic)を頻繁に書くようになった。
初めは、railsのviewのhelperが強力なので、極力erb使って、template書かないように、と考えていた。
生html
ところが、生html書くのが思った以上に、(railsのviewと比較すると)快適。
何を言っているかというと、railsのviewのhelperって、メソッド名やオプションの細かい仕様が独特で、頭に入らず、いつもググってる自分がいて、
これが、生htmlになると、殆どそのままガリガリ書けるので、生産性が高い。
また、出来上がった物も、確かに、railsと比べると冗長さはあるのだが、明快で変な動きをする要素が無い。
なので、生html化していくのも悪くは無い。
※完璧にrailsのviewのhelper暗記してたらそれがいいかもだが
特に親子階層があって、通信&イベントが発生する所は、初めからMarionettteのCollectionとItemViewを使わないと、最終的に大変になるので、初めからバラしてしまうべき。
Viewと紐づけるセレクタはView内のelに書かず、regionから持って来る。
上のerbの話の続きになるが、全体の見取り図となるviewは、erbに書いておくべき。最初に書いておけば、初期処理の段階で、region書いてしまえる。
Viewクラス内には、el書かず、必ずregionから持って来るようにすると、 region見れば、全部のViewを、見渡せるので、概要掴み易いし、作業もし易くなる。
コンポーネント間の連携event
生Backboneを書いてる時、どうしても連携の都合で、1Viewに複数のModelを持ってて、複雑なbindingを書いたりしてしまった。
これは、罪深い事で、間違っていたと思う。
非常に複雑になってしまった。こういうのがオレオレ実装。
1Viewは関連する1つのリソースだけ、それとbindするべき。Marionetteのイベントもthis.model/this.collectionしか書けない。この仕組みに従うと綺麗になる。
イベント連携の悩み
そうすると、今度は、イベント連携どうするか、悩む。 この解決策は多分複数あるが、書いてて行き着いたのは、
- viewのイベントであればMarionetteのtriggers
- それ以外は、Applicationのイベント(reqres/cmd含む)。
そうして、以下の様なイメージで動かした。
- それぞれのViewはそれぞれのViewに関係する事しか行わない前提。
- 関係ない事は、trigger or Applicationのイベントを発火!
- このApplicationのイベントは、担当しているControllerのAction内でbindしている。
- 大抵、そこから、他のViewに働きかける。
こうすると、とにかく、Controller見れば、大体の全体の流れが掴める!
うん。railsと一緒だ。
Controllerあれば、一々、親となるApplicationViewみたいなの作る必要もない。
なんか、めっちゃスッキリした。
ここは、異論でそう。
stickit
前にも書いたけど、stickitが非常に良い。
これも、フォームと通信を伴うような、普通のアプリであれば、目を瞑って初めから導入してしまうべき。
ただ、注意すべきは、動きが見えなくなる点。やっぱり、想定外の動きをする事がある。
有り難くない2way binding
stickitは2way bindingを適用してくれるのだが、View => modelはとても有り難いのだが、 model => Viewの更新が、非同期でsave()してる時に邪魔になる事がよくある。
※編集中の項目が元に戻ったりとかね。作り次第の話なのだが。
こういうシチュエーションは、updateView: falseばんばん設定して1wayにするようにしてる。
テキストフィールド以外の動きが怪しい
テキストフィールドの動きはばっちり同期するのだが、これが、radioやcheck_box等、
他のinputだと、おかしな動きをする事がある。
これも、stickitの方でデフォルトで準備してくれてるハズなのだが、
chromeならOKだけど、FFでうまく値取ってくれてない時があったりした。
これは、もう仕方ないので、getVal()内で調整コード書いた。
※これは、いつかpull req出すかも。気が向いたら。
なんか困ったらgetVal上書きしよう。
終わり
また、書いてみたら長くなってしまった。
Marionette + sitckitで一通りの事は苦も無く使えるようになった。
動きが分からない所はソースをgrepすれば解決してしまう。
他の複雑な動きをするframeworkと比べるとこういう気軽な所が良い。
事例も沢山、困った時にググると情報も沢山ある。
他のframework色々流行ってますが、まだまだBackboneも使えそうです。
ちなみに、日本とオランダは2-2で、引き分け!惜しい!