Mithril.js を試してみた
-
jQuery はなんだか最初は簡単だなぁ、と思って使ってみたが、やっている内に、泥臭いし、たくさんコードを書かないといけないし、DOMに追加したり、削除したり、順番を変えたり、保持しているデータとDOMを同期させる仕事をやるだけで疲れてきて、嫌いになった。
-
Angular.js (Google) も試してみて、最初は感動したけど、なんかいろいろと難しくて挫折した。
-
React.js (Facebook) は、絶対これが本命だと思って、すごく期待して、本まで買って試していたが、ちょっと覚える事が多くて、どうしようかと迷っていた。
-
Riot.js もちょっと試そうかと思ったけど、やっぱり覚える事が多くて、大変そうに思えた。
-
Mithril.js は、なんか思った通りに書ける気がする。覚える事が少ない。軽い。速い。
JavaScriptオンリーでHTMLもCSSも書ける感じだ。
現時点で Virtual DOM を使うフレームワークは正解だと思う。
jQuery は初心者の勉強のためにはいいけど、僕の中では使っちゃダメなものとして位置付けられている。
追加記事を書いた。
Mithril.js 試してみた(1) todo アプリを作り始める所まで - Qiita ⇒ この記事
[Mithril.js 試してみた(2) サーバーからデータを取得する m.request() - Qiita]
(http://qiita.com/LightSpeedC/items/f533f048e01ac19a06ec)
[Mithril.js 試してみた(3) console.logの様に画面に表示してみる - Qiita]
(http://qiita.com/LightSpeedC/items/23dcecfa22b89dc7c165)
[Mithril.js 試してみた(4) todo アプリのフロント側まで - Qiita]
(http://qiita.com/LightSpeedC/items/c3026847dc5809d2a7a9)
[Mithril.js 試してみた(5) Excelの様な表計算アプリを3時間で作る m.component() - Qiita]
(http://qiita.com/LightSpeedC/items/c19677822f896adc43d9)
いつの間にか日本語のサイトが...
知らない間に http://mithril.js.org/ の日本語訳サイトが作られていた。
スゴイ。すばらしい。ありがたい。
↓
http://mithril-ja.js.org/ (http://shibukawa.github.io/mithril-ja/)
本も出てるじゃん
Mithril――最速クライアントサイドMVC
http://www.oreilly.co.jp/books/9784873117447/
サンプル https://github.com/oreilly-japan/mithril-book-sample
とりあえず最小限のAPIだけでViewだけのサンプルアプリを作ってみた
ダウンロードした mithril.min.js
を使用する。
以下のサンプルはm.mount()
とm.draw()
しか使用していない。
<!DOCTYPE html>
<meta charset="UTF-8">
<title>counter minimum - Mithril.js</title>
<script src="mithril.min.js"></script>
<!--[if IE]><script src="es5-shim.min.js"></script><![endif]-->
<body></body>
<script>
//カウンター
var counter = 0;
//タイマーでカウントアップ
setInterval(function () {
counter++;
m.redraw(true);
}, 1000);
//ビュー
function view() {
return 'count: ' + counter;
}
//HTML要素にコンポーネントをマウント
m.mount(document.body, {view: view});
</script>
ルール無視のサンプルだ。真似してはいけない。
Viewから外の変数を見ているし、外から再描画の指示があるし、
例としてはあまり良くないかな。
IE8でも動く様に es5-shim.min.js
を以下から入手した。
es5-shim - github
ControllerとViewだけのアプリ
ひどい例だったので、もうちょっと改良してみる。
<!DOCTYPE html>
<meta charset="UTF-8">
<title>counter prop - Mithril.js</title>
<script src="mithril.min.js"></script>
<!--[if IE]><script src="es5-shim.min.js"></script><![endif]-->
<body>
<div id="$counterElement"></div>
</body>
<script>
//コンポーネント定義
var counterComponent = {
//コントローラ
controller: function () {
var ctrl = this;
//カウンター
ctrl.counter = m.prop(0);
//タイマーでカウントアップ
setInterval(function () {
ctrl.counter(ctrl.counter() + 1);
m.redraw(true);
}, 1000);
},
//ビュー
view: function (ctrl) {
return m('div', 'count: ' + ctrl.counter());
}
};
//HTML要素にコンポーネントをマウント
m.mount($counterElement, counterComponent);
</script>
ちょっと変えてみた。
ControllerとViewだけのアプリだ。
m.prop()
も試しに使ってみた。
この例ではm.prop()
もあまり役に立つように見えないがm.withAttr()
と一緒に使うといいよ。
TIPS: ちなみに
document.getElementById('element')
とかjQueryの$('#element')
というのは
シンプルにelement
と省略できるよ。知ってた?
本当はwindow.element
やwindow['element']
という事なんだ。
だからJavaScriptから扱いたいHTMLエレメントのidにはハイフンとか使わないで
JavaScriptの変数名の規約に合わせておくと扱いやすいよ。
<div id="$elementName">
の様にidを$で始めると$elementName
という変数で扱えるようになる。jQuery使ってるみたいでいいよね。
※古いブラウザで使うにはbodyタグが閉じていないといけないけどね。
※グローバル変数がたくさんできるので注意が必要だけどね。
controller関数
controller
関数は、コンストラクタとして呼ばれている。
だから return {counter: m.prop(0)};
の様にオブジェクトを作って返してもいいみたいなんだけど、どちらかと言うとthis
のプロパティに追加するパターンを使う方がいいかなぁ、と思う。
どちらでもいいか。好みの問題か。
view関数とm()
view
関数は、文字列でも、数字でも、配列を返しても、m()
の結果のオブジェクトを返してもいいらしい。
めちゃめちゃ柔軟性があるなぁ。m()
の引数の後半も同様に柔軟性がある。
return 'count: ' + counter; // 文字列
return [m('div', 'count: '), m('div', counter)]; // m('div')を2つ持つ配列
return m('div', 'count: ' + counter); // m()のコンテンツは文字列
return m('div', 'count: ', counter); // m()のコンテンツは文字列と数字
return m('div', ['count: ', counter]); // m()のコンテンツは配列
return m('div', [['count: '], [[[counter]]]]); // m()のコンテンツは複雑な配列
viewで2つのHTMLエレメントを返すためにdiv
で囲む必要もない。配列で十分だ。
※文字列を配列で返す場合はDOMとしてテキストノード要素がわかれているみたい。
サンプルのToDoアプリをちょっと改良してみた
例題になっているToDoアプリを改良して、テキスト入力時にEnterキーを受け付けるようにしてみた。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ToDo App - Mithril.js</title>
<script src="mithril.min.js"></script>
<!--[if IE]><script src="es5-shim.min.js"></script><![endif]-->
</head>
<body>
<div id="$todoElement"></div>
</body>
<script>
//コンポーネント定義
var todoComponent = function () {
'use strict';
// モデル: Todoクラスは2つのプロパティ(title : string, done : boolean)を持つ
function Todo(data) {
this.title = m.prop(data.title);
this.done = m.prop(false);
};
// モデル: TodoListクラスはTodoオブジェクトの配列
var TodoList = Array;
// コントローラ・オブジェクトctrlは
// 表示されているTodoのリスト(list)を管理し、
// 作成が完了する前のTodoのタイトル(title)を格納したり、
// Todoを作成して追加(add)が可能かどうかを判定し、
// Todoが追加された後にテキスト入力をクリアする
// このアプリケーションは、todoComponentコンポーネントでコントローラとビューを定義する
var todoComponent = {
// コントローラは、モデルの中のどの部分が、現在のページと関連するのかを定義している
// この場合は1つのコントローラ・オブジェクトctrlですべてを取り仕切っている
controller: function (listArg) {
var ctrl = this;
// アクティブなToDoのリスト
ctrl.list = new TodoList();
// 引数に渡されたtodoタイトルリストからToDoのリストに追加
if (listArg) {
listArg.forEach(function (title) {
ctrl.list.push(new Todo({title: title}));
});
}
// 新しいToDoを作成する前の、入力中のToDoの名前を保持するスロット
ctrl.title = m.prop('');
// ToDoをリストに登録し、ユーザが使いやすいようにtitleフィールドをクリアする
ctrl.add = function () {
var val = ctrl.title() ||
document.getElementById('$title').value; // for ie8
if (val) {
ctrl.list.push(new Todo({title: val}));
ctrl.title('');
}
return false;
};
},
// ビュー
view: function (ctrl) {
return [
m('form', {onsubmit: ctrl.add},
m('input', m_on('change', 'value', ctrl.title, {id: '$title'})),
m('button[type=submit]', {onclick: ctrl.add}, 'Add')),
m('table', [ctrl.list.map(function(todo) {
return m('tr', [
m('td', [
m('input[type=checkbox]', m_on('click', 'checked', todo.done))
]),
m('td', {style: {textDecoration: todo.done() ? 'line-through' : 'none'}}, todo.title())
]);
})])
];
}
};
// HTML要素のイベントと値にプロパティを接続するユーティリティ
function m_on(eventName, propName, propFunc, attrs) {
attrs = attrs || {};
attrs['on' + eventName] = m.withAttr(propName, propFunc);
attrs[propName] = propFunc();
return attrs;
}
return todoComponent;
}();
// アプリケーションの初期化
// HTML要素にコンポーネントをマウント
m.mount($todoElement, m.component(todoComponent, ['first task', 'second task']));
// m.mount($todoElement, todoComponent);
</script>
</html>
controller
関数に引数を追加して、m.component()
を使ってみた。
m.component()
を複数の場所で使える部品にするには、コツが必要な気がする。
P.S.
Chrome, Firefox, ie8, ie11, Edge で動作確認しました。
React.js の JSX の様な MSX というのもあるが、そちらが好きな人はそちらをどうぞ。