Virtual DOM がアツい昨今ですが、virtual DOM は特に Reactやdekuのようなコンポーネントライブラリの実装に使われているということで注目を集めています。
一方、virtual DOM自体は概念にすぎないので、それを独立して実装しているvirtual-domなどのライブラリもあります (以前の記事で書きました)。
そこで、その virtual-dom をベースにReactのようなコンポーネントライブラリを自作してみるのも興味深いと思い試してみました。
作ったものの概要
- virtual-dom ベース
- ビューと状態を一つにまとめたコンポーネント
- 簡単化のためにstates/propsの区別はなし
- 同じ場所/移動しているが同じキー の時は同一コンポーネントがそのまま生き残る
- 各コンポーネントごとに独立してdiff/patch
- パフォーマンスのため。見たところdekuも同じことをやっている (Reactは大きすぎて今のところ読めていない…)
- これは素のvirtual-domだとやりにくく、virtual-domの
Widget
ノードをうまく使うと実装できる - Reactと違って、外側のコンポーネントが更新された時に内側のコンポーネントも更新されるようにはしていない (パフォーマンスは良くなるけど使い勝手は悪くなるかも?)
- 本物のDOMをコンポーネントとして扱うこともできる
(今のところ)CoffeeScriptで200行程度のかなり簡単な実装です。
今のところ、CoffeeScriptのクラスを使いまくっているので、ES5以前のJSで使うとつらそうです。
例
{Component} = require 'decompose'
class TodoView extends Component
render: ->
h 'li', [
h 'h2', @todo.title
]
class TodoListView extends Component
render: ->
h 'ul', @todos.map (todo) =>
TodoView.render todo: todo
todoListView = new TodoListView(todos: todoCollection.todos)
todoCollection.on 'update', ->
todoListView.update()
new Mount(todoListView).mount('#main')
TodoMVC
定番のTodoMVC実装をこのライブラリでも書いてみました。
実装
Component
コンポーネントのベースクラスです。
Mount
実DOM上にマウントします。1イベントループ中にComponentがupdate
イベントを1回以上吐くと、イベントループ完了時にdiff/patchが行われます。
ComponentNode
virtual-domのdiff/patch用に使われます。
パフォーマンス
(@dsuket さんの https://github.com/dsuket/todomvc-perf-comparison をフォークさせていただきました)
手元の環境 (OS X 10.10.1 + Chrome 39) だとなぜかTodoMVCでいい感じのパフォーマンス(一番下)が出ます(バグかもしれませんが…)
機能を増やしたり使い勝手をよくしていくとどうなるかは今のところわかりません。
まとめ
- virtual-domベースで単純なコンポーネントシステムを簡単に作ることができた
- パフォーマンスも結構出るっぽい?