lxyuma BLOG

開発関係のメモ

Ractive.js入門

これは、jsCafe18で話したSlideです。

概要

これから、「Ractive.js」という

js上でReactiveなUIを

とてもsimpleにbuildするためのlibraryの話をします

目次

  • reactive programming
  • ractive.js
  • 他のframeworkとの比較
  • Backboneに入れてみる

そもそもReactive Programmingとは?

古くは、2003年頃から日本のblogでも幾つか言及されてる模様

去年、更に色々と動きが出てきた。

バズりと意味の広がり

  • なんか徐々にバズり感がある
  • 他のバズりワード(cloud等)と同じように意味が広義に、より抽象的になってきてる
  • マニフェストにあるような広義な意味でのReactiveは一旦今日は置いておく

Reactive programmingの例

2010年に素晴らしい記事を書かれてるmaoeのブログを参考にjsで書かせて頂くと

var a = 1;
var b = a + 1;
a = 10;
console.log(b) // => 11
  • aの値の変化からbの値が動的に変化した。
    • ※勿論、今のjsはこんな動きしない。
  • 一見、何言ってるか意味分からないかもしれないが、以下のExcelのセル計算と同じimage
    • A1セルに =B2+C3と書くと
    • A1セルの内容はB2とC3が加算されて、動的に書き換えられる。

結局Reactive programmingとは

  • 時間や外部入力と共に変化する値や計算を、
  • プログラムするのではなく、
  • システム側が見えない所で反応(reaction)してくれるもの

用途は、色々あるようだが、GUIプログラミングに向いてる。

ここまで、maoeのブログの記事の要約。ありがとうございました。

GUIとReactive

Reactive Programmingとjavascript

jsでのReactive関連libはRxJS/bacon.js/knockout/Meteor等色々ある。

アプローチも様々。

偶々、「BackboneとReactive」で検索してると

なんか、Backbone本家のGithubで、面白そうなissueひっかかった。

いい加減な訳

Github issue

Q: Ractive.jsとBackbone使うのって良いIdeaじゃない?

我々のアプリはHandlebarsとBackbone使ってる。
幾つかのViewの中に維持の必要な多くの要素があるがお互い非常に強く結びついている。
これはsubviewに分ける事を困難にしている

私はDOM要素を操作する事やレガシーコードをリファクタリングする事に疲れた。
そこで、Ractive.jsを見つけた。
これは、これらの問題を解決してくれるために開発されている。
それは、Angularみたいだが、ただAngular程にクレージーではない。

Angularは開発中の殆どの問題を壊してくれるように思う。それは素晴らしい事かもしれない。
しかし、私のようないくらかの人々にとって学習する事が厳しい。

そこで、私の質問は、次の通り。
・Backboneの中で、Ractive.jsと一緒にtemplateを使う形で置換するのは良いのでは?

Ractive.jsとは

theguardian.comで新しいアプリを作るために開発されたらしい(去年2013年の夏頃)

  • ReactiveなUIをビルドするjsのライブラリ
  • 特定フレームワークを強制させる事はしない
    • なので、Backboneと組み合わせようが自由(実際、公式pageにbackboneとの連携例もあがってる)

つづり

  • ちなみに、つづりは、「ract」ive.js
    • reactiveでないので、ご注意を
    • React / reactive.js等等同じ様な名前の物がいっぱいある

情報源

特徴

公式pageによると

  • データバインディング
    • 美しい宣言的なシンタックス
  • イベントハンドリング
    • 泣かせたり髪をかきむしったりさせない
  • 柔軟でパフォーマンスの高いアニメや移動ができる

根本的にDOM操作のアプローチと異なる。

angularあるでしょ

  • 非常にangularに近い感じ。
    • 実際、かなり意識されて作られている
    • 開発時の呼称がAnglebarsだったらしい(Angular + Handlebars) ※ロゴまであった。
  • 大きな違いは、DI等特殊な事をractive.jsはやってない事
    • ractiveは非常にsimpleな文法になっていて覚える事は少ない(1hでtutorial終わる)

knockoutがあるでしょ

  • ractive.js = databindできる軽量のlibrary
  • 立ち位置がknockoutそっくり
    • ここの記事のコメントでも戦ってる
    • 作者はknockoutの文法が気に入らないらしい(言うだけあって、ractiveはkoと比べてもsimple)
  • ここの比較記事によると
    • ractiveは、internalなDOM持っていて全要素のre-renderせず、DOMを1つずつでなくグループでまとめ非同期に更新できる所が違うらしい
  • 後、そもそもractiveはMV* FWでないので、ModelとかViewとかいう言葉が出てこない

data-bindingsの2つの方式

  • dirty-checking(ex. angular)
    • 前の値比較,変化あれば変更eventをfire
    • simpleなobjectを扱えてsimpleな点がメリットだが、処理として無駄多い
  • change listeners(ex.knockout)
    • 動的な処理をする関数の中にある監視objを追跡してその関数を入れるといった事が必要(依存性追跡)
    • simpleなobjectでなく決まった口から呼ぶ必要があり、また、個別にfireするので複数まとめて処理する時に効率的でない
  • 他、色々メリデメがある。※参考:stackoverflow slide
    • ractive.jsは後者(ちなみにまとめて処理は出来る※多分updateの話)(また、後述するが依存性追跡の仕組みはちょっと違う)

要するに

  • Ractive.js = 複雑な所がないAngular
  • Ractive.js = knockoutと立ち位置が近く、仕組みも近いが、もっとsimple

ractive.jsを始める

    var ractive = new Ractive({
      el: "#result",
      template: "#myTemplate",
      data: {
        user: '太郎',
        messages: { total: 11, unread: 4 }
      }
    });

sample(html側)

  • 出力先
    <div id="result">
    </div>
  • テンプレート
  <script id='myTemplate' type="text/ractive">
    <p>こんにちわ{{user}} さん。
      <strong>{{messages.unread}} 件の未読メッセージがあります。</strong>
    </p>
  </script>

sampleを動かす

  • sampleを動かすと、templateが出力されている
  • console上で、以下をやってみるとbrowserの値も変わってるはず
    • ractive.set('messages.unread', 5);

sampleのjsを見てみる

    var ractive = new Ractive({
      // el : 出力先の要素
      el: "#result",

      // template : 表示の際元となるテンプレート
      template: "#myTemplate",

      // data : Reactiveに値を変えて行きたいデータの固まり
      data: {
        user: '太郎',
        messages: { total: 11, unread: 4 }
      }
    });
  • ぱっと見もごちゃごちゃしてなく、たった3つのプロパティしかない。

sampleのtemplateを見てみる

  <script id='myTemplate' type="text/ractive">
    <p>こんにちわ{{user}} さん。
      <strong>{{messages.unread}} 件の未読メッセージがあります。</strong>
    </p>
  </script>
  • mustacheの書き方を採用している
    • {{ }} で変数等
    • この例には書いてないが、mustacheの他の文法がそのまま使える(#等)
  • jsで定義していたdataの中身を変数として使ってる
    • usermessages.unread

consoleでやった事を振り返る

ractive.set('messages.unread', 5);
  • 作ったinstanceのdata内の変数に値をsetしてる
    • 単純に内部の値が変わるだけでなく、画面上の要素も変わってる

ractiveのデータの取得変更の整理

  • 取得したい
    • ractive.get( ‘messages.unread’ )
  • 変更したい
    • 方法1)前述の ractive.set('messages.unread', 5);
    • 方法2)reactive.update(‘message’)

変更の方法2の例

var msg = ractive.get("messages");
msg.unread = 9; 
ractive.update("messages");

仕組み

  • ※参考

    • Ractiveは、jsonみたいなツリーの中でtemplateをparse
    • 式を見るとAbstractSyntaxTreeを作る(参照を抜き出す)
    • mustacheをrenderする時参照を解決
Ractive.parse( '{{i+1}}' );

// {
//   t: 2,          // mustacheの型
//   x: {           // 式
//     r: [ "i" ],  // 式の中の参照
//     s: "${0}+1"  // 参照を含む式
//   }
// }

仕組み

  • 単純な文字展開(遅いeval繰り返し)する仕組みと比べて冗長なようだが、
    • 関数を一度で作るだけで済む
    • メモリに効果的だし、パフォーマンスも良い

dynamic attributeの使い道

  • 例えば、cssの切り替えもできる
<p style='color: {{color}}; font-size: {{size}}em; font-family: {{font}};'>
  {{greeting}} {{recipient}}!
</p>

関数

  • 勿論、関数も追加できる。
    • 例えば、数値を整形するformat関数を作る
<p>The population of {{country}} is {{ format(population) }}.</p>
  data: {
    country: 'the UK',
    population: 62641000,
    format: function ( num ) {
      if ( num > 1000000000 ) return ( num / 1000000000 ).toFixed( 1 ) + ' billion';
      if ( num > 1000000 ) return ( num / 1000000 ).toFixed( 1 ) + ' million';
      if ( num > 1000 ) return ( Math.floor( num / 1000 ) ) + ',' + ( num % 1000 );
      return num;
    }

イベント

  • 1クッション挟む(proxy event)
    • html側の属性に on-イベントハンドラ名=‘イベント名’
    • js側で reactive.on(‘イベント名’, function(event) {})
<button on-click='activate'>Activate!</button>
ractive.on( 'activate', function ( event ) {
  alert( 'Activating!' );
});

mustache系で出来る事

mustacheで出来る一通りの事がRactive.jsでも出来る

// true,falseチェック = #
{{#signedIn}}
  <p>You’ve logged in</p>
{{/signedIn}}
// ※これで、ractive.data.signedIn = trueの時に、表示される
// not = ^
{{^signedIn}}<!-- not-signed-in block -->{{/signedIn}}
// これで、ractive.data.signedIn = falseの時に、表示される

list操作

これもmustache

  {{#people}}
    <tr>
      <td>{{name}}</td>
      <td>{{age}}</td>
    </tr>
  {{/people}}
// reactiveの中
data : {
  people : [
    {name : “taro”, age : 18},
    {name : “hanako”, age : 20} ] }

listに追加

people.push(newPerson);

他にpop/shift/等準備されてる

tutorial

ここまで書いた内容がtutorialに書かれてるので、良ければ。(1hで終わる)

BackboneとRactive.js

公式pageの例にもある位、setで使うのに問題は無さげ。

  • Backbone adapterを使うと、ModelとViewを繋いでくれるみたい
    • src見ると、Model/Collectionのchangeイベントにひっかけてractive.setしてる

色んな妄想

  • (´-`).。oO(これ、listも勝手にsyncしてるからMarionetteのCollectionViewとか全部要らなくなるんじゃね?
  • (´-`).。oO(これ、viewにそのまま変数貼付けれるからViewのstate管理が綺麗になるんじゃね?
  • (´-`).。oO(これ、jsから見て完全にDOM分かれてるからテストし易いようなimage。

Backbone的に可能性を感じる

backboneと一緒に書いてみた

  • 要するに、BackboneがAngularっぽくなった。
    ractive.TaskListView = Ractive.extend({
        template: "#task-list-template",
        init: function(options){
            this.set('taskList', []);
            this.on({
                onClickTaskItem: function(e, item){
                    item["finished"] = true;
                    this.update("taskList");
                }
            });
        },
        pushNewTask: function(task){
            this.get('taskList').push(task.attributes);
        }

書いてみたhtml

         <div id="task-list">
            <script id="task-list-template" type="javascript/ractive">
                <ul>
                {{#taskList}}
                    <li on-click="onClickTaskItem:{{this}}" style="{{.finished ? 'text-decoration:line-through;' : ''}}">
                        {{name}}
                    </li>
                {{/taskList}}
                </ul>
            </script>
        </div>

変わった事

  • Before:Backboneで親子階層書くのが手間(Marionette使ってもやっぱり大変)
    • After:ractiveのmustacheのlist表示で一発
  • Before:BackboneでView内のstate管理(Modelにする程ではないような物)が大変だった
    • After:ractiveのtemplate内の分岐で解決
  • Before:BackboneのView内でDOM操作が絡まる(Marionette.View.ui使ってもカオス)
    • After:ractiveはデータだけ触れば良いのでごちゃごちゃしない(test書き易そう)

もう一度言うけど

  • Angularでよくね?
    • DI等キモい所が無いから良い
  • knockoutでよくね?
    • knockoutとBackbone繋ぐ例は少ないが、(knockback?そこまで要らない)ractiveはMV*でもなく、小さいので簡単に入れれる。(公式でも例あるし。)
    • ractiveの方が見た目綺麗。

個人的な感想(良い所)

  • 要するに、elとtemplateとdataの3つとmustacheなので、すげーsimple。不可解な所が無い
  • Backbone
    • Marionetteもstickitも要らなくなるかも…
    • Backbone.Viewと併用するなら$elの生成timing考えないといけないかもだが、ractiveでeventもまとめて書ける事等から、そもそもBackbone.View要らなくなりそう(routerとmodel通信部分とevent連携だけBackboneから借りる??)

駄目な所

  • 個人的にmustache自体キモい(普段ejs使ってる事もあり)
    • mustacheへの独自機能追加もキモい(../で親階層辿れる??何言ってるの?)
    • templateエンジンを自分で選べないのは苦痛
  • 情報量の少なさと使ってる人の少なさ
  • knockoutとの棲み分けをもっと明確にした方が良いと思う

もう少しRactive.js使ってみる事にする

おしまい。