Mithril 1.0.1

はじめよう

Mithrilとは?

MithrilはクライアントサイドのJavaScript MVCフレームワークです。このツールを使うと、アプリケーションのコードはデータレイヤ(Model)、UIレイヤー(View)、接続レイヤー(Controller)に分割されます。

小さく、ムダのないAPIのおかげで、Mithrilはgzipされた状態でたったの7.8kbしかありません。Mithrilは、ハイパフォーマンスなレンダリング速度を持つ、仮想DOMの差分更新機能を持ったテンプレートエンジンや、関数型によるハイレベルなモデリングのサポート、ルーティング機能、コンポーネント化をサポートしています。

このフレームワークのゴールは、アプリケーションコードの探索しやすさ、読みやすさ、メンテナンス性を向上させ、あなたがすばらしい開発者になる手助けをすることです。

他のフレームワークと異なり、Mithrilはクモの巣のような依存関係にとらわれないようにするために多大な努力を払っています。フレームワーク内の必要な部分だけを使うことができます。

しかし、このツールセットのレールに乗ることで多くの利益を得ることができます。その中には、実用的なシナリオ上で関数型プログラミングを学んだり、しっかりとしたオブジェクト指向プログラミングやMVCの良いコーディングプラクティスを学ぶことも含まれます。


シンプルなアプリケーション

Mithrilのインストールをしたらコーディングを開始しましょう。Mithrilは、決まり文句がとても少ない、小さなHTMLから開始することができます:

<!doctype html>
<title>Todoアプリケーション</title>
<script src="mithril.min.js"></script>
<body>
<script>
//アプリケーションコードがここに追加されます
</script>
</body>

そうです。これは完全なHTML5です。HTMLの規格によると、<html><head>タグは省略できますが、ブラウザがマークアップをレンダリングするときに暗黙的にそれぞれのDOMエレメントを生成します。


モデル

Mithrilでは基本的に、アプリケーションは名前空間の中に作り、コンポーネントをその中に格納していきます。コンポーネントは見ることが可能な「ページ」もしくはページの一部を表す、単なる構造体です。アプリケーションはModel、Controller、Viewの大きく3つのレイヤーにきれいに分割することができます。

サンプルを簡単にするために、これから作るアプリケーションにはコンポーネントを1つだけ作ることにします。そしてそれをアプリケーションの名前空間として使っていきます。

Mithrilにおいて、コンポーネントview関数と、controller関数(オプション)を含むオブジェクトです。

//空のMithrilコンポーネント
var myModule = {
    controller: function() {},
    view: function() {}
}

コンポーネントはコントローラとビューを保持するだけではなく、関連するデータを格納するために使用されます。

それではコンポーネントを作ってみましょう。

<script>
//このアプリケーションはtodoコンポーネントのみを含む
var todo = {};
</script>

モデルの実体は再利用可能なため、コンポーネントの外で定義されることが多いです (例: var User = ...)。今回のサンプルではすべてのアプリケーションが1つのコンポーネントに収まっているため、コンポーネントを名前空間として使い、モデルの実体もその中にいれてしまいましょう。

var todo = {};

//例をシンプルにするために、このコンポーネントをモデルクラスの名前空間として利用する

//Todoクラスはプロパティを2つ持つ
todo.Todo = function(data) {
    this.description = m.prop(data.description);
    this.done = m.prop(false);
};

//TodoListクラスはTodoのリスト
todo.TodoList = Array;

m.propはgetter-setter関数を作るための、単純なファクトリです。getter-setterは次のように動作します:

//'John'を初期値として、getter-setterを定義する
var a_name = m.prop("John");

//値を取得する
var a = a_name(); //a == "John"

//値を`Mary`に設定する
a_name("Mary"); //Mary

//値を取得する
var b = a_name(); //b == "Mary"

上記のサンプルで定義したTodoクラスとTodoListクラスは、混じりけのない、プレーンなJavaScriptのコンストラクタです。これらのクラスは次のように初期化して使います:

var myTask = new todo.Todo({description: "コードを書く"});

//descriptionを取得する
myTask.description(); //コードを書く

//完了しているか?
var isDone = myTask.done(); //isDone == false

//完了にする
myTask.done(true); //true

//ここでは完了している
isDone = myTask.done(); //isDone == true

TodoListクラスは単に、標準のArrayクラスの別名です。

var list = new todo.TodoList();
list.length; //0

昔ながらのMVCパターンの定義によると、モデルレイヤはデータの保持、状態の管理、ビジネスロジックについての責務を負っています。

上記のサンプルのモデルクラスは、この基準を満たしていることがわかるでしょう。意味のある状態を構成するのに必要なメソッドとプロパティを十分に備えています。Todoは初期化をして、プロパティの変更を行うことができました。配列の方はpushメソッドを使ってTodoの項目を追加することができます。他にもあります。

ビュー・モデル

次のステップとして、モデルクラスを使用する、ビュー・モデルを書きます。ビュー・モデルはモデルレベルのエンティティで、UIの状態を保持します。多くのフレームワークでは、UIの状態はコントローラの内部に保持されます。しかし、コントローラはデータプロバイダとしてデザインされていないため、このような構造にしてしまうと、コードをスケールさせるのが難しくなります。Mithrilでは、UIの状態というのはデータベースのORMエンティティにマップされる必要はないが、モデルデータとなるべきもの、と考えます。

ビューモデルはUI固有の制約に関するビジネスロジックを処理する責任を持ちます。例えば、テキスト入力とキャンセルのボタンを持っているフォームがあるとします。このケースでは、ビュー・モデルは、オリジナルの状態とテキスト入力の現在の状態の差のトラッキングと、キャンセル処理の提要に関する責任を持ちます。フォームが保存されるというイベントが発生したら、ビュー・モデルはより適切なORMモデルエンティティに保存処理を移譲します。

われわれのToDoアプリケーションでは、ビュー・モデルに必要とされるものはそれほど多くありません。アクティブなToDoのリストと、新しいToDoを追加するためのフィールドの管理、ToDoを追加するロジックや、UIのアクションとの連携です。

//ビュー・モデルの定義
todo.vm = {
    init: function() {
        //アクティブなToDoのリスト
        todo.vm.list = new todo.TodoList();

        //新しいToDoを作成する前の、入力中のToDoの名前を保持するスロット
        todo.vm.description = m.prop('');

        //ToDoをリストに登録し、ユーザが使いやすいようにdescriptionフィールドをクリアする
        todo.vm.add = function(description) {
            if (description()) {
                todo.vm.list.push(new todo.Todo({description: description()}));
                todo.vm.description("");
            }
        };
    }
};

上記のコードは、vmという名前のビュー・モデルオブジェクトを定義しています。ビュー・モデルオブジェクトは、init関数を持つ、シンプルなJavaScriptのオブジェクトです。このコードは、3つのメンバーを持つvmオブジェクトを初期化しています。単純な配列のlist、初期値の文字列として空の文字列を渡されたm.propのgetter-setter関数であるdescription、入力のdescription getter-setterが空の文字列でないときに、新しいTodoインスタンスをlistに登録するadd関数です。

このガイドの後半では、descriptionプロパティをこの関数の引数として渡します。準備が整ったところで、なぜこのサンプルではオブジェクト指向プログラミングのスタイルのメンバーの関連付けを行わずに、descriptionを引数として渡すのかを説明しましょう。

ビュー・モデルは次のように使えます:

//ビュー・モデルの初期化
todo.vm.init();

todo.vm.description(); //[空文字列]

//Todoを登録してみる
todo.vm.add(todo.vm.description);
todo.vm.list.length; //空のdescriptionは登録できないので、0

//正しく登録できた
todo.vm.description("コードを書く");
todo.vm.add(todo.vm.description);
todo.vm.list.length; //1

コントローラ

クラシカルなMVCでは、ビューからやってきたアクションを、モデルのレイヤーにディスパッチするのがコントローラの役割です。伝統的なサーバーサイドフレームワークでは、HTTPのリクエスト、レスポンスおよび、開発者に公開されているフレームワークの抽象化という性質を考えると、コントローラレイヤはとても大きな役割を持つレイヤです。コントローラはHTTPのリクエストからシリアライズされたデータを取り出して変換して、ORMのモデルのメソッドに渡したりします。

クライアントサイドMVCではこのような定義との不一致は存在しないため、コントローラは極めてシンプルになります。Mithrilのコントローラは、モデルレベルの機能の中からいくつか限定したものだけを公開するという、最小限の機能にまで落とし込まれています。ここで今まで出てきたロールを思い出してみてください。モデルはビジネスロジックのカプセル化、ビュー・モデルは特定のUI状態に関するロジックのカプセル化を行います。抽象化された役割の中でコントローラが行うべきものは、モデルの中から、現在の表示されているものを切り出して表示するという役割以外は残っていません。

Mithrilのコントローラが行うべきことは、次のコードがすべてです:

todo.controller = function() {
    todo.vm.init()
}

ビュー

次に、ユーザとアプリケーションがインタラクションするためのビューを書きましょう。MithrilではビューはプレーンなJavaScriptコードです。このことには、エラー発生時のメッセージが分かりやすいとか、適切なレキシカルスコープが使えるなどのさまざまな利点があります。また、HTML文法を使うことができるプリプロセッサツールも利用できます。

todo.view = function() {
    return m("html", [
        m("body", [
            m("input"),
            m("button", "追加"),
            m("table", [
                m("tr", [
                    m("td", [
                        m("input[type=checkbox]")
                    ]),
                    m("td", "タスクの説明"),
                ])
            ])
        ])
    ]);
};

m()というユーティリティ関数はvirtual DOMの要素を作成します。CSSセレクタを使って要素を選択することもできます。.文法を使ってCSSのクラスを追加したり、#を使ってidを追加することもできます。

MSXHTML文法プリプロセッサを使っていなくても、本来のセマンティック表現の恩恵を受けられるため、m(".modal-body")のようなCSSセレクタ形式を使うことを推奨しています。

コードをテストする目的で表示したい場合は、m.renderメソッドを呼ぶことでビューをレンダリングすることができます:

m.render(document, todo.view());

renderメソッドを呼び出す時は、テンプレートそのものに加えて、ルートのDOM要素を渡します。

このメソッド呼び出しをすると、次のようなマークアップが生成されます:

<html>
    <body>
        <input />
        <button>追加</button>
        <table>
            <tr>
                <td><input type="checkbox" /></td>
                <td>タスクの説明</td>
            </tr>
        </table>
    </body>
</html>

m.renderはMithrilの提供する中で、とても低レベルなメソッドで、1回だけ描画するために使います。自動再描画システムを利用する場合は使用しません。自動再描画システムを利用する場合は、 m.mountを使ってtodoコンポーネントを初期化するか、m.routeを使ってラウト(route)を定義する必要があります。MithrilはKnockout.jsのようなObservableベースのフレームワークとは異なり、m.propのgetter-setterに値を設定しても、再描画が起動されるような副作用は発生しないことに注意してください。


データバインディング

次に、テキスト入力にデータバインディングを実装していきましょう。データバインディングは、DOM要素とJavaScriptの変数変数を結びつけて、片方の変更をもう片方に反映させるものです。

//モデルの値と、テンプレート内のテキスト入力を結びつける
m("input", {value: todo.vm.description()})

このコードにより、description getter-setterとテキスト入力が接続されました。モデルの中のdescriptionの値を更新すると、Mithirlが再描画を行ってDOMのテキスト入力を更新します。

todo.vm.init();

todo.vm.description(); // 空文字列
m.render(document, todo.view()); // テキスト入力もブランク

todo.vm.description("コードを書く"); // ビュー・モデルのdescriptionに値を設定
m.render(document, todo.view()); // テキスト入力に「コードを書く」が表示される

一見すると、このコードは再描画を何度も行っていて、とても実行コストが高そうに見えますが、todo.viewメソッドを何度呼び出しても、実際にはテンプレート全体が再描画されることはありません。Mitril内部では、仮想的に表現されたDOMをキャッシュとして持っていて、変更を検知して変更に必要な最小限の更新だけどDOMに対して行います。そのため、極めて高速に再レンダリングを行うことができます。

上記の場合、Mithrilはテキスト入力のvalue属性しか変更しません。

このコードでは、DOMのテキスト入力エレメントの値に設定するだけで、読み込みは行わないことに注意してください。これは、何かしらのテキストを入力して、再レンダリングが実行されると、スクリーン上のテキストが破棄されることを意味します。


幸い、バインディングは双方向で行うように設定することができます。双方向になると、DOMの値を設定するだけでなく、ユーザが入力した内容を読み込んで、ビューモデル内のdescription setter-getterの内容を更新することができます。

次のコードが、ビューからモデルへのバインディングを行う、最小限の実装になります:

m("input", {onchange: m.withAttr("value", todo.vm.description), value: todo.vm.description()})

このコードはonchangeに結び付けれており、「valueの属性の値を, todo.vm.descriptionに設定」するという意味になります。

Mithrilはどのイベントを監視して更新を受け取るかはユーザに任せています。onchangeonkeypressoninputonblurなど、好きなイベントを使用できます。

また、どの属性と結びつけるかも自由に設定できます。<select>タグであれば、value属性と結びつけることもできますし、用途によってはselectedIndexプロパティとバインドすることもできます。

m.withAttrはMithrilが提供する関数型プログラミングのためのツールで、ビューの内部で無名関数の使用頻度を下げます。

m.withAttr("value", todo.vm.description)という関数呼びしは、次のコードに相当する関数を返り値として返します:

onchange: function(e) {
    todo.vm.description(e.target["value"]);
}

無名関数の使用を避ける以外の違いとしては、m.withAttrを使うと、正しいイベントターゲットや、適切なデータソースを選んでくれます。例えば、その入力ソースがJavaScriptのプロパティか、DOMElement::getAttribute()かといった違いを識別して、適切な動作をします。


双方向バインディング以外の機能では、パラメータ付き関数をイベントにバインドすることもできます。

var vm = todo.vm

m("button", {onclick: vm.add.bind(vm, vm.description)}, "追加")

上記のコードは、標準的なJavaScriptのFunction::bindメソッドを使っています。このメソッドを使うと、パラメータがあらかじめ設定された、新しい関数を作ることができます。関数型プログラミングの世界では、これは部分適用(partial application)と呼ばれます。

vm.add.bind(vm, vm.description)という式は、次の式と等価です:

onclick: function(e) {
    todo.vm.add(todo.vm.description)
}

パラメータを束縛するときに、上記の例ではdescription getter-setterを参照で渡していて、直接値を渡しているわけではありません。Mithrilは、getter-setterをcontrollerメソッド内でしか評価しません。このような形式を遅延評価(lazy evaluation)と呼びます。これを使うと、「イベントハンドラが呼ばれて、本当に必要になったタイミングまで値の取得を遅らせよう」ということができます。

これまでの説明の中で、なぜMithrilがm.propを使うことを推奨しているのか、お気づきの方もいると思います。Mithrilのgetter-setterは関数です。それにより、関数型プログラミングの道具と、相性良く組みわせることができて、強力な書き方が使えるようになります。上記の例の場合は、Cのポインタのような機能を実現しています。

Mithrilは、これ以外のさまざまなところでも、これらのテクニックを使っています。

賢い読者の方は、addメソッドをもっとシンプルなコードにリファクタリングすることができることに気づいたかもしれません:

vm.add = function() {
    if (vm.description()) {
        vm.list.push(new todo.Todo({description: vm.description()}));
        vm.description("");
    }
};

変更前のコードと異なっているのはaddメソッドの引数がなくなった点です。

これにより、テンプレートのonclickバインディングをもっとシンプルに書くことができます:

m("button", {onclick: todo.vm.add}, "追加")

部分適用を使って説明したのは、単にこのようなテクニックが使えるということを紹介する目的でした。これを使うと、パラメータを持つイベントハンドラの可能性が広がります。実際のコーディングでは、いくつか選択肢があった場合には、ユースケースを実現するもっともシンプルな方法を選択すべきです。


JavaScriptの配列のメソッドを使うだけで、Mithrilのビューでフローコントロールを実装することができます:

//これがビュー
m("table", [
    todo.vm.list.map(function(task, index) {
        return m("tr", [
            m("td", [
                m("input[type=checkbox]")
            ]),
            m("td", task.description()),
        ])
    })
])

上記のコードのtodo.vm.listはJavaScriptの配列です。mapは関数型プログラミング的な機能を提供する、標準の関数です。このメソッドを使うと、配列の全要素に対して変換処理を行い、その結果の要素で構成される配列を返します。

このメソッドは、ご覧のとおりに、2つの<td>タグを持つ、部分テンプレートを返しています。2つ目のタグは、Todoクラスのインスタンスdescription getter-setterへのバインディングが含まれています。

ここまでの説明でお気づきの通り、JavaScriptには、関数型プログラミングに対する強力なサポートが備わっています。これを使うと、他のフレームワークではなかなかうまく表現しづらいコードを、とても自然な形で記述することができます。例えば、<dl>/<dt>/<dd>を構成するためにループを使うといったことができます。


残りのコードは、これまで説明してきたテクニックで書くことができます。完成したviewは次のようなコードになっています:

todo.view = function() {
    return [
        m("input", {onchange: m.withAttr("value", todo.vm.description), value: todo.vm.description()}),
        m("button", {onclick: todo.vm.add}, "追加"),
        m("table", [
            todo.vm.list.map(function(task, index) {
                return m("tr", [
                    m("td", [
                        m("input[type=checkbox]", {onclick: m.withAttr("checked", task.done), checked: task.done()})
                    ]),
                    m("td", {style: {textDecoration: task.done() ?"line-through" : "none"}}, task.description()),
                ])
            })
        ])
    ];
};

このテンプレートの大事なポイントを個別に説明していきます:


最初に紹介したときは、データを変更した後に手動でm.renderを呼び出して再描画を行わせていました。しかし、前に説明したとおり、m.mountを通じてtodoコンポーネントを初期化すると、自動再描画システムが有効になります。

//todoコンポーネントをbody DOMノード内にレンダリングします
m.mount(document.body, {controller: todo.controller, view: todo.view});

Mithrilの自動再描画システムはコントローラの安定性を崩しません。ビューの再描画はコントローラの処理がすべて終わった時にのみ行われます。このコントローラの処理には、AJAXによるデータのロードも含まれます。同様に、このシステムはイベントハンドラ内部の非同期サービスについても、再描画前にきちんと完了することを賢く待ちます。

どのように再描画の仕組みが動作するかはこちらのドキュメントでより詳しく知ることができます。


まとめ

完全なアプリケーションのコードはこちらになります:

<!doctype html>
<script src="mithril.min.js"></script>
<body>
<script>
//このアプリケーションはtodoコンポーネントだけで構成されています
var todo = {};

//サンプルを簡単にするために、このコンポーネントはモデルクラスの名前空間としても使います

//Todoクラスは2つのプロパティを持ちます
todo.Todo = function(data) {
    this.description = m.prop(data.description);
    this.done = m.prop(false);
};

//TodoListクラスはtodoの配列
todo.TodoList = Array;

//ビュー・モデルは表示されているTodoのリストを管理し、
//作成が完了する前のTodoの説明を格納したり、
//作成が可能かどうかを判定するロジックや、
//Todoが追加された後にテキスト入力をクリアする責務を持つ
todo.vm = (function() {
    var vm = {}
    vm.init = function() {
        //アクティブなToDoのリスト
        vm.list = new todo.TodoList();

        //新しいToDoを作成する前の、入力中のToDoの名前を保持するスロット
        vm.description = m.prop("");

        //ToDoをリストに登録し、ユーザが使いやすいようにdescriptionフィールドをクリアする
        vm.add = function() {
            if (vm.description()) {
                vm.list.push(new todo.Todo({description: vm.description()}));
                vm.description("");
            }
        };
    }
    return vm
}())

//コントローラは、モデルの中のどの部分が、現在のページと関連するのかを定義している
//この場合は1つのビュー・モデルですべてを取り仕切っている
todo.controller = function() {
    todo.vm.init()
}

//これがビュー
todo.view = function() {
    return [
        m("input", {onchange: m.withAttr("value", todo.vm.description), value: todo.vm.description()}),
        m("button", {onclick: todo.vm.add}, "追加"),
        m("table", [
            todo.vm.list.map(function(task, index) {
                return m("tr", [
                    m("td", [
                        m("input[type=checkbox]", {onclick: m.withAttr("checked", task.done), checked: task.done()})
                    ]),
                    m("td", {style: {textDecoration: task.done() ?"line-through" : "none"}}, task.description()),
                ])
            })
        ])
    ]
};

//アプリケーションを初期化します
m.mount(document.body, {controller: todo.controller, view: todo.view});
</script>
</body>

このサンプルは、jsFiddleでも確認することができます。これらに関する拡張済みのサンプルもjsFiddleで提供しています。


アーキテクチャに関するノート

Mithril風のコードとは、良いプログラミングの規約に従っていて、リファクタリングしやすいコードです。

上記のコードのTodoクラスは、もしコードを再構成する必要が出てきたとしても、とても簡単に他のコンポーネントに移動できます。

Todoのコードは他のコードなどへの依存もなく、疎結合でまとまっています。jQueryベースのコードと違って、DOMと密結合していません。TodoクラスのAPIは再利用可能で、ユニットテストが容易です。また、プレーンなJavaScriptのクラスであるため、フレームワーク特有の学習は必要ありません。

m.propはシンプルですが、とても幅広く使えるツールです。関数型のパラダイムを使えるようにしたり、データアクセスの統一化をしたり、リファクタリングが必要になったときに簡単に行えるように高度な疎結合化をするのに使えます。

リファクタリングが避けられなくなったら、m.prop呼び出しを、適切なgetter-setter実装に置き換えるだけで済みます。プログラム全体をgrepしてAPIを使っている箇所を検索して置き換える必要はありません。

例えば、ToDoの説明(description)が大文字でなければならないとしたら、description getter-setterだけを置き換えればこれが実現できます:

this.description = m.prop(data.description)

変更後:

//プライベート変数
var description;

//パブリックなgetter-setter
this.description = function(value) {
    if (arguments.length > 0) description = value.toUpperCase();
    return description;
}

//シリアライズ可能にする
this.description.toJSON = function() {return description}

//値を設定
this.description(data.description)

サンプルのビュー・モデルの中で、TodoListクラスを単なる配列のエイリアスとして定義しました。ネイティブのArrayクラスを使ったお陰で、標準のArrayクラスのメソッドを自分自身のAPIとして暗黙的に使えるようになりました。

既に慣れ親しんでいるAPIが使えるようになったトレードオフとして、カスタムの動作を行うことができなくなっています。例えば、リストを永続化させる機能をアプリケーションに追加したくなったとすると、ネイティブのArrayクラスはもっとも適切なクラスとは言えません。

TodoListのようなクラスをリファクタリングする時は、ArrayクラスのAPIの一部だけをサポートするか、似た機能を持つまったく他のクラスを実装するか決定しなければなりません。

上記のコードの場合は、新しいクラスに必要なメソッド.push()メソッドと.map()メソッドだけです。APIをフリーズしたり実装を交換したりするだけであれば、リファクタリングを行う際に他のレイヤをまったく触る必要はなくなります。

todo.TodoList = Array;

変更後:

todo.TodoList = function () {
    this.push = function() { /*...*/ },
    this.map = function() { /*...*/ }
};

要件は時間とともに変化しますが、これらの例から、Mithrilがどのような哲学を持って、開発者がコードベースをリファクタリングする際に、どのように標準的なオブジェクト指向プログラミングのテクニックを使って欲しいと思っているか、アプリケーションを大きな塊にしないで小さくして欲しいと考えているのかが伝われば幸いです。


これがもっともはっきりと気づいてもらいやすい点が、ビューがHTMLに書かれていないという点です。

ぱっと見ると、これはとても奇妙な設計に見えますが、実際には多くのメリットがあります:

それでもHTMLの文法を使いたいということであれば、MSXと呼ばれるパッケージが使えます

Mithrilのビューは仮想DOMの差分実装を使用していて、いくつかのフレームワークが持っている不透明要素のダーティチェックやパフォーマンス上の問題といった問題を解決しています。

m()ユーティリティが持つ他の機能としては、CSS的な記述方法で完結にテンプレートが記述できます。これは一般的なサーバサイドMVCフレームワークのHTML処理と似ています。

MithrilのビューがJavaScriptであることから、破壊的なコンポーネントに対する双方向バインディングヘルパーといった、抽象的な一般化のパターンから開発者が開放されます。一般的なJavaScriptのリファクタリングテクニックが利用できます。

Mithrilのテンプレートは他のコンポーネントシステムよりも衝突に対して安全です。アドホックにタグ名を決定してHTMLタグの名前空間を汚すことができないようになっています。

Mithril実装の中で、これよりも知的で面白い点としては、m.withAttrm.propや、ネイティブの.bind()メソッドを使って部分適用させていくことで、イベントハンドリングが関数合成の文脈で行うことができる点です。

もし関数型プログラミングを学習したり、実世界で使ってみたいと考えているのであれば、Mithrilがとても実践的な場を提供してくれるでしょう。


より詳しく学ぶ

Mithrilには、このページ内で紹介しきれなかった機能がまだいくつかあります。Mithrilについてより深く知りたい場合には次のトピックが良い出発点になります。

上級トピック

その他


License: MIT. © Leo Horie.