とにかくシンプルで簡単で高速なViewのコンポーネントを作れるHorn.jsを作った

JavaScriptでリアクティブっぽくとにかく短くシンプルにビューが書けるライブラリを作った。

mizchi/horn.js https://github.com/mizchi/horn.js

名前は背骨とか角とかっぽければ何でもよかった。

方針

  • Angularっぽいユーザー定義のdirective
  • Knockout.js っぽいデータバインド
  • Backbone風の軽量なAPI
  • 依存はjQueryのみ

とにかく短くビューのコンポーネントの塊を書けるのを目的とした。UnityとかFlashのコンポーネントに影響を受けている気がする。 HTMLのテンプレートによって、ビューがどういう属性を持つか決定される。それをJavaScriptから使う。

インストール

bower install horn

使い方

こんな感じでテンプレートを用意する

<div
  data-template-name="status"
  data-attrs="name, money, showAddMoney">

  <span data-text="name">NO NAME</span>
  <span data-text="money">0</span>
  <button data-click-with-trigger="update">update</button>
  <button data-click="toggleShowAddMoney">toggle show add money</button>
  <button data-visible="showAddMoney" data-click="addMoney">addMoney</button>
</div>

(ここで生のHTMLを扱うのは、何かしらのテンプレートエンジンによって生成されることを想定している)

このViewはアトリビュートとしてname, money, showAddMoneyを持つ。 data-clickはクリックされたときに紐付けられたインスタンスのメソッドを呼ぶ。これはJavaScript側から必要な振る舞いを記述することで完成する。

# 振る舞いの定義
class Status extends Horn.View
  templateName: 'status'
  addMoney: ->
    @money += 10

  toggleShowAddMoney: ->
    @showAddMoney = !@showAddMoney

# 使う
status = new Status
status.name = "名無し"
status.money = 0

# 画面に置く
status.attach 'body'

値を書き換えるだけでdata-bindされたビューのプロパティが書き換わる。リアクティブっぽい感じに値オブジェクトをgetterでラップしてる。

ボタンをクリックするたびに、toggleShowAddMoney が呼ばれる。showAddMoney が書き換わると、data-visible属性がそれを監視して表示が切り替わる。 基本的にclassは使わない。マークアップ用に分離したい。

この実装の意味

こういうアプリたくさん開発してきたけど、JavaScriptとテンプレートが一意に紐づくものしか書けないので、セマンティクスとか捨てて、テンプレート側で属性を決定して、JavaScriptでそれを補完してやればいいと思う。

Backbone.stickit、データバインディングでお決まりのこと書くのが多すぎてだるい。だったらもうテンプレートに変数とのマッピングを書いてしまいたい。

昔作ったskin.jsと発想は似てる。それと mizchi/liteview っていう自作の軽量viewフレームワークがあって、これを混ぜたりこねたりしてdirective風味の何かを定義したらhorn.jsができた。

ajaxやモデルにまつわる機能は何もない。とにかく意味があるビューが振る舞いもつ単位を短く書けるようにした。

リストビュー

この手のやつ、とにかくリストビューがだるい。リストビューが簡単に書けるのを目的にした。

# ListView has itemView
class StatusList extends Horn.ListView
  itemView: Status

# ListView
list = new StatusList
list.size(2) # generate automatically 2 blank view.
list.update [{name: 1},{name: 2},{name: 3}] # generate automatically 3 view and apply params.
list.addItem {name: 4} # add more
list.get(1).dispose() # remove 2nd
list.attach 'body'

リストビューの基本的な発想は、内部で現時点の要素数だけバッファを用意して、データを適用していく。 インスタンスはは使いまわす。

細かくプロパティ弄りたかったら一旦toJSONしてupdateに突っ込み直せばいい。

data = list.toJSON()
data.forEach (item) -> item.money += 100
list.update data

サンプル

簡単なTODOアプリをサンプルとして用意した。 https://github.com/mizchi/horn-todo-example brunchで構築してある。gruntは使ってない。本当はTODO MVC準拠で作ろうとしたけど、本筋以外が思った以上に面倒臭い感じがしたので、諦めた。

MV*に思うところ

Angular分厚すぎて学習コストが高くてだるい。Bacbkoneはお決まりのこと書くのがだるい。knockoutは筋がいいけど値オブジェクトが関数なのがださい。とにかくいずれもリストビュー周りがだるすぎる!!!!

じゃあ軽量テンプレートに軽量directiveに軽量バインディングがあればいいじゃん!!!やったーーー!!!!

TODO

  • 抽象度低くしてるので、APIがこなれてない。とくに、Horn.registerTemplateとか。
  • ドキュメント
  • テスト
  • ベンチマーク
  • CoffeeScriptから使うことしか考えてない
  • 入れ子になってdata-bindがぶつかったときのことを考慮してない

IE8は対応してない。対応したけりゃObject.definePropertyの部分をdefineGetterで書き換える必要あるけど、この時代その必要性を感じない。

現場からは以上です。