m.startComputation / m.endComputation
多くの場合、アプリケーション空間からm.startComputation
/ m.endComputation
を呼ぶ必要はありません。これらのメソッドは非同期のタスクを実行するライブラリを作成したり、素のJavaScriptの非同期関数をテンプレートのconfig
関数から呼び出す人のみが使うことを想定しています。
MithrilのAPIを使わないで、カスタムの非同期呼び出しを行った時に、ビューが更新されていない時は、m.startComputation
/ m.endComputation
を使用することを検討してください。カスタムコードの処理が終わった時にMithrilが賢く自動再描画を行います。
非同期のコードとMithrilの自動再描画システムを統合する時は、非同期処理を呼び出す「前に」m.startComputation
を呼び出し、非同期のコールバックの最後でm.endComputation
を呼び出してください。
//このサービスは1秒間待ってログに"hello"と出力し、その後ビューに
//再描画を行うように知らせています (他の非同期処理により延期されないかぎり)
var doStuff = function() {
m.startComputation(); //`startComputation`は非同期の`setTimeout`の前に呼び出す
setTimeout(function() {
console.log("hello");
m.endComputation(); //`endComputation`はコールバックの最後で呼び出す
}, 1000);
};
同期処理のコードと統合する場合は、メソッドの先頭でm.startComputation
を呼んで、最後にm.endComputation
を呼んでください。
window.onfocus = function() {
m.startComputation(); //イベントハンドラの先頭で、他の処理よりも先に呼ぶ
doStuff();
m.endComputation(); //イベントハンドラの最後で、他の処理よりも後に呼ぶ
}
ライブラリ内のそれぞれのm.startComputation
呼び出しに対して、「かならず」「1回だけ」m.endComputation
を呼んでください。
setInterval
を使う場合など、コードが別々に呼ばれるような場合はこれらのメソッドは使わないでください。もし、ユーザの入力を待つことなく、繰り返し再描画を行いたい場合は、繰り返し可能なコンテキストの中で、手動でm.redraw
を呼び出してください。
startComputation
を呼んだ後に、対になるendComputation
呼び出しをすると、再描画システム全体が動作しなくなるので注意してください。例外が発生する可能性のあるコードをtry
ブロックで囲み、対応するm.endComputation
をfinally
ブロック内で呼ぶようにすると、再描画システムが停止することを防げます。
window.onfocus = function() {
m.startComputation();
try {
doStuff();
}
finally {
m.endComputation(); //`doStuff`が例外を投げたとしても再描画が行われる
}
}
どのように再描画が行われるか
Mithrilの自動再描画システムは、m.prop
getter-setterの値の変更では何もしません。Mithrilは再描画のタイミングを、m.startComputation
とm.endComputation
の呼び出しで決めます。
Mithrilは内部カウンタを持っています。このカウンタはm.startComputation
が呼ばれるたびに増加し、m.endComputation
が呼ばれると減少します。カウンタがゼロになるとMithrilは再描画を行います。Mithrilは、m.mount
、m.route
、m.request
の中と、m()
で定義されたイベント呼び出しの中で、この関数のペアを呼びます。
m.request
をコントローラのコンテキスト内で複数回呼ぶと、内部カウンターが増加します。リクエストが完了するたびにカウンタが減少します。すべてのリクエストが完了するのを待って、Mithrilは再描画を行おうとします。サードパーティのライブラリや、ネイティブJavaScriptのコードから非同期関数を使う時も、これらの関数のペアを呼ぶ場合は同じルールが適用されます。
Mithrilがすべての非同期サービスの待ち合わせをするのは、何度もムダなブラウザ再描画を行わないようにしたり、テンプレート内でnull参照チェックを行う回数を減らす、という理由によるものです。
m.request
のbackground
オプションを使用したり、Mithril外の非同期関数を呼ぶ時は単にm.startComputation
/ m.endComputation
の呼び出しをしなければ再描画のスケジューリングをやめ、再描画が積極的に行われます。
//`background`オプションのサンプル
var component = m.component({
controller: function() {
//`background`を設定すると、リクエストの完了を待たずにすぐに再描画を行う
m.request({method: "GET", url: "/foo", background: true})
},
//...
})
m.redraw.strategy
を使うと、任意で行う再描画の戦略を変更することができます。再描画戦略の変更は次に計画されている再描画にのみ影響を与えます。再描画が完了すると、Mithrilは再描画がの原因が他のアクションにあるかどうかをを元にして、m.redraw.strategy
の設定を"all"か"diff"に変更します。
//ゼロから再描画するのではなく、ラウト変更時にも差分検知をする
//ラウト変更後も`<input>`エレメントと、それに追加されたサードパーティプラグインが維持される
var Component1 = m.component({
controller: function() {
m.redraw.strategy("diff")
},
view: function() {
return m("div", [
m("h1", "Hello Foo"),
m("input", {config: plugin}) //`plugin`がサードパーティのライブラリを初期化するものとする
])
}
})
var Component2 = m.component({
controller: function() {
m.redraw.strategy("diff")
},
view: function() {
return m("div", [
m("h1", "Hello Bar"),
m("input", {config: plugin}) //`plugin`がサードパーティのライブラリを初期化するものとする
])
}
})
m.route(document.body, "/foo", {
"/foo": Component1,
"/bar": Component2,
})
//モデル
var saved = false
function save(e) {
if (e.keyCode == 13) {
//デフォルトでイベントハンドラは自動再描画を起動するため再描画が行われる
saved = true
}
else {
//他のキーについては無視したいので、再描画も行わない
m.redraw.strategy("none")
}
}
//ビュー
var view = function() {
return m("div", [
m("button[type=button]", {onkeypress: save}, "保存"),
saved ?"Saved" : ""
])
}
computationメソッドとm.redrawの違い
m.startComputation
/ m.endComputation
のペアは「スタックされる」ように設計されています。例えば、複数の非同期サービスのそれぞれがこの呼出のペアを持っており、すべてのリクエストが完了するまで再描画を待たせることができます。これとは対照的にm.redraw
は「アグレッシブ」です。このメソッドは、呼ばれた回数だけ再描画を行おうとします(実際には、同一フレーム内の呼び出しはまとめます)。m.redraw
を使うと、AJAX呼び出しの完了前に再描画を実行させることができます。データの存在のチェックを行わないと、null参照例外が発生する恐れがあります。
そのため、複数の非同期アクセスが完了する前に再描画が即座に行われないようにするために、computationメソッドを使うほうが推奨です。
computationメソッドを使うと、未完了のデータをテンプレートが利用することがなくなります。しかし、テンプレート内でnullになりえる可能性がある場合は常にチェックコードを書くことが大切です。新しく導入したサードパーティライブラリがm.redraw
を呼び出す可能性がありますし、何かしらの機能を実装する時に積極的な再描画を行う必要が出てくる可能性があるからです。
nullになる可能性がある場合の対処法としては、m.request
にinitialValue
オプションを指定するか、基本的なnullチェック(例: data ?m("div", data) : null
)を行う方法があります。
複数実行スレッドの統合
サードパーティ製のライブラリを統合する時に、MithrilのAPIの外で非同期メソッドが使っているものもあるでしょう。
自明でない非同期処理のコードとMithrilの自動再描画システムを統合するときには、確実にすべてのスレッドでm.startComputation
/ m.endComputation
を呼び出す必要があります。
実行スレッドは基本的に、他の非同期スレッドが実行する前に、ある程度の量のコードを含んでいます。
複数の実行スレッドのコードと統合を行うには2つの方法があります。階層に分けて行う方法と、統括的に行う方法です。
階層に分けて統合
たくさんのさまざまなAPIがアプリケーションレベルで使われている時は、階層に分けて統合する方法がおすすめです。
次のサンプルは、多くのメソッドがサードパーティ製ライブラリを使っているコードを、階層に分けて統合したコードになります。メソッドの中には個別に使われるものもあれば、組み合わせて使われるものもあります。
doBoth
はdoSomething
とdoAnother
から呼び出されるため、m.startComputation
を何度も呼んでいます。このコードは完璧に正しいコードです。jQuery.when
が呼ばれた後に、そこから3組のm.startComputation
/ m.endComputation
呼び出しがあるため、3つの非同期描画が遅延されて、バッチ処理されます。
var doSomething = function(callback) {
m.startComputation(); //`startComputation`は非同期のAJAXリクエスト前に呼ぶ
return jQuery.ajax("/something").done(function() {
if (callback) callback();
m.endComputation(); //`endComputation`はコールバックの最後で呼び出す
});
};
var doAnother = function(callback) {
m.startComputation(); //`startComputation`は非同期のAJAXリクエスト前に呼ぶ
return jQuery.ajax("/another").done(function() {
if (callback) callback();
m.endComputation(); //`endComputation`はコールバックの最後で呼び出す
});
};
var doBoth = function(callback) {
m.startComputation(); //非同期の同期メソッドの前に`startComputation`を呼び出す
jQuery.when(doSomething(), doAnother()).then(function() {
if (callback) callback();
m.endComputation(); //`endComputation`はコールバックの最後で呼び出す
})
};
統括的な統合
非同期の操作が、常に同じ手順で使われている場合には、統括的な統合がおすすめです。階層に分けた統合と比べると、m.startComputation
/ m.endComputation
呼び出しが最小限になります。
次のサンプルには、サードパーティライブラリを使った複雑に入り組んだAJAXリクエストのかたまりがあります。
var doSomething = function(callback) {
m.startComputation(); //すべての処理の前に`startComputation`を呼び出す
jQuery.ajax("/something").done(function() {
doStuff();
jQuery.ajax("/another").done(function() {
doMoreStuff();
jQuery.ajax("/more").done(function() {
if (callback) callback();
m.endComputation(); //すべての処理の後に`endComputation`を呼び出す
});
});
});
};
レガシーコードとの統合
もし、個別のウィジェットを同じページ内の異なる場所に置きたい場合には、通常のMithrilアプリケーションと同じように、m.render
、m.mount
、m.route
などを使ってそれぞれのウィジェットを初期化することができます。
1つだけ注意すべきことがあるとしたら、単純にこれら複数の「島」を初期化してしまった時に、それぞれの初期化呼び出しがお互いを認識せずに、再描画処理が想定上に発生してしまう、ということを避けなければならない、ということです。レンダリングを初期化するには、それぞれの実行スレッドの中で、最初のウィジェットの初期化の前にm.startComputation
を呼び出し、最後のウィジェットの初期化の後にm.endComputation
を呼び出すだけです。
m.startComputation()
m.mount(document.getElementById("widget1-container"), Widget1)
m.mount(document.getElementById("widget2-container"), Widget2)
m.endComputation()
シグニチャ
void startComputation()
void endComputation()