Mithril 1.1.0

ライフサイクルメソッド


使用方法

コンポーネント仮想DOMノードはライフサイクルメソッドを持つことができます。これはフックとも呼ばれ、DOM要素の生存期間のさまざまなポイントで呼ばれます。

// コンポーネントのフックのサンプル
var ComponentWithHook = {
    oninit: function(vnode) {
        console.log("コンポーネントを初期化")
    },
    view: function() {
        return "hello"
    }
}

// vnodeのフックのサンプル
function initializeVnode() {
    console.log("vnodeを初期化")
}

m(ComponentWithHook, {oninit: initializeVnode})

すべてのライフサイクルメソッドはvnodeを最初の引数として受け取ります。また、thisキーワードはvnode.stateに束縛されます。

ライフサイクロメソッドはm.render()呼び出しの副作用として呼び出されます。Mithrilの外側でDOMを変更してもライフサイクロメソッドは呼び出されません。


DOMエレメントのライフサイクル

DOMエレメントは作成されてからドキュメントに追加されます。DOMエレメントは属性や子ノードを持ちます。UIイベントがトリガーされると、それらが変更されるでしょう。また、ドキュメントから取り除かれることもあります。

エレメントが取り除かれると、一時的にメモリプールの中に保持されます。プールされた要素は、その後の更新時のDOMリサイクリングと呼ばれるプロセスの中で再利用される可能性があります。エレメントを再作成するとパフォーマンスに影響を与えるコストが発生します。最近存在した要素をリサイクルすると、このコストを減らすことができます。


oninit

oninit(vnode)フックはvnodeが仮想DOMエンジンから触れる前に呼び出されます。oninitはDOMエレメントがドキュメントに追加される前に呼ばれることが保証されています。また、親のvnodeは子供のvnodeよりも先に呼ばれることが保証されています。しかし、親や子孫のDOM要素が存在する保証はありません。oninitメソッドの中から、vnode.domにアクセスしてはいけません。

このフックはエレメントが更新されるときには呼ばれませんが、エレメントがリサイクルされる時には呼ばれます。

他のフックと同じようにoninitコールバック内では、thisvnode.stateを指します。

oninitフックはvnode.attrsvnode.childrenに渡された引数を元にコンポーネントを初期化するのに便利です。

var ComponentWithState = {
    oninit: function(vnode) {
        this.data = vnode.attrs.data
    },
    view: function() {
        return m("div", this.data) // 初期化時に保存したデータを表示
    }
}

m(ComponentWithState, {data: "Hello"})

// 次のHTMLと同等
// <div>Hello</div>

このメソッド内から同期的にモデルのデータを変更すべきではありません。oninitは他の要素の状態を保証しないため、このメソッドからモデルを変更しても次のレンダリングサイクルまで、他のUI部品ではこの変更が反映されない可能性があります。


oncreate

oncreate(vnode)フックはDOMエレメントがドキュメントに追加された時に呼ばれます。oncreateはレンダリングサイクルの最後に呼ばれることが保証されています。そのため、vnode.dom.offsetHeightといったレイアウトに関する属性値にアクセスしたり、vnode.dom.getBoundingClientRect()といったメソッドを正常に利用できます。

このフックはエレメントが更新される時には呼ばれません。

他のフックと同じようにoncreateコールバック内では、thisvnode.stateを指します。oncreateフックを持つvnodeに関連するDOMエレメントはリサイクルされません。

oncreateフックは、再描画を誘発するような属性へのアクセスをしたり、アニメーションを開始したり、DOM要素を必要とするサードパーティライブラリを初期化するのに使えます。

var HeightReporter = {
    oncreate: function(vnode) {
        console.log("初期化中。DOMの高さは: ", vnode.dom.offsetHeight)
    },
    view: function() {}
}

m(HeightReporter, {data: "Hello"})

このメソッド内から同期的にモデルのデータを変更すべきではありません。oncreateはレンダリングサイクルの最後に実行されるため、このメソッドからモデルを変更しても次のレンダリングサイクルまで、他のUI部品ではこの変更が反映されません。


onupdate

onupdate(vnode)フックはDOMエレメントがドキュメントに追加されている間に更新がかかると呼ばれます。onupdateはレンダリングサイクルの最後に呼ばれることが保証されています。そのため、vnode.dom.offsetHeightといったレイアウトに関する属性値にアクセスしたり、vnode.dom.getBoundingClientRect()といったメソッドを正常に利用できます。

このフックは、以前のレンダリングのサイクル時にすでにノードが存在している時にのみ呼ばれます。このフックはエレメントが作成されたり、エレメントがリサイクルされる時には呼ばれません。

他のフックと同じようにonupdateコールバック内では、thisvnode.stateを指します。onupdateフックを持つvnodeに関連するDOMエレメントはリサイクルされません。

onupdateフックは再描画を誘発するような属性にアクセスしたり、モデルデータが変更された時にサードパーティーライブラリにその変更を反映して動的にUIを更新する時に使います。

var RedrawReporter = {
    count: 0,
    onupdate: function(vnode) {
        console.log("再描画回数: ", ++vnode.state.count)
    },
    view: function() {}
}

m(RedrawReporter, {data: "Hello"})

onbeforeremove

onbeforeremove(vnode)フックはDOMエレメントがドキュメントから取り外される前に呼ばれます。もしPromiseが返されると、MithrilはそのPromiseが完了されたあとに取り除きます。

このフックはDOMエレメントがparentNodeを失う時にのみ呼ばれます。その子要素に対しては呼ばれません。

他のフックと同じようにonbeforeremoveコールバック内では、thisvnode.stateを指します。onbeforeremoveフックを持つvnodeに関連するDOMエレメントはリサイクルされません。

var Fader = {
    onbeforeremove: function(vnode) {
        vnode.dom.classList.add("fade-out")
        return new Promise(function(resolve) {
            setTimeout(resolve, 1000)
        })
    },
    view: function() {
        return m("div", "Bye")
    },
}

onremove

onremove(vnode)フックはDOMエレメントがドキュメントから取り外される時に呼ばれます。もしonbeforeremoveフックが定義されていると、onremoveonbeforeremoveが完了した後に呼び出されます。

このフックはその要素が親から直接削除される要素であったり、他の削除されるエレメントの子供であっても、ドキュメントから削除されるときに呼び出されます。

他のフックと同じようにonremoveコールバック内では、thisvnode.stateを指します。onremoveフックを持つvnodeに関連するDOMエレメントはリサイクルされません。

onremoveは後片付けを行うタスクに最適です。

var Timer = {
    oninit: function(vnode) {
        this.timeout = setTimeout(function() {
            console.log("タイムアウト")
        }, 1000)
    },
    onremove: function(vnode) {
        clearTimeout(this.timeout)
    },
    view: function() {}
}

onbeforeupdate

onbeforeupdate(vnode, old)は更新時にvnodeの差分比較の前に呼び出されます。もしこの関数が定義されてfalseを返すと、Mithrilは差分検知をスキップします。その子要素もスキップされます。

このフック自体は、サブツリーがコンポーネント内にカプセル化されていない限り、仮想DOMサブツリーが生成されることを抑制しません。

他のフックと同じようにonbeforeupdateコールバック内では、thisvnode.stateを指します。

このフックは、とても巨大なDOMツリーの更新時のラグを減らすのに役立ちます。


アンチパターンを避ける

Mithrilは制約が少なく柔軟ですが、いつくか非推奨のコードパターンがあります。

早すぎる最適化を避ける

onbeforeupdateによる差分検知のスキップは最終手段です。パフォーマンスの問題が最優先出ない限りは使うのを避けましょう。

通常、onbeforeupdateで修正できるパフォーマンス上の問題は、要素が多すぎる巨大な配列が原因です。この文脈では「大きな」とは、多数のノードを含む巨大なスプレッドシート(悪名高い5000行のテーブル)か、深くて密なツリーの配列のことです。

パフォーマンス上の問題がある場合は、最初にUIが提供するユーザーエクスペリエンスが適切かどうかを検討し、そうでない場合は変更してください。たとえば、ユーザーが5000行の表を調べる可能性は非常に低く、ユーザーに最も関連性の高い項目のみを常時する検索機能を使用する方が良いでしょう。

設計の改善で解決できず、多数のDOM要素を持つUIを最適化する必要がある時は、最大配列の親ノードで onbeforeupdateを適用し、パフォーマンスを検証してください。ほとんどの場合、1回のチェックで十分です。たまにこのケースに合わせないこともありますが、onbeforeupdate宣言は注意深く使わなければなりません。onbeforeupdateがいくつも登場するのであれば、デザインワークフローに重大な問題が発生しているはずです。

その場限りの最適化をアプリケーション内で流用してもいけません。コードが多くなればなるほど保守コストが上がります。 onbeforeupdate関連のバグは、オブジェクトIDで条件チェックを行う必要があるため、トラブルの解決が特に難しくなります。

繰り返しになりますが、onbeforeupdateフックは最後の手段としてのみ使用してください。


License: MIT. © Leo Horie.