Mithril 1.1.0

変更履歴


v1.1.0

ニュース

バグ修正


v1.0.1

ニュース

バグ修正


v0.2.xからの移行

v1.xv0.2.xと幅広くAPIの互換性がありますが、破壊的変更もいくつか行われています。

もし移行を考えている時はmithril-codemodsの使用を検討してください。このツールはほとんどのシンプルな移行を自動化するツールです。


m.propが削除された

v1.xでは、m.prop()はより強力でコンパクトなストリームとしてライブラリ化されました。Mithrilのコアからは外れました。オプションであるストリームモジュールの使用方法は、こちらのドキュメントで読むことができます。

v0.2.x

var m = require("mithril")

var num = m.prop(1)

v1.x

var m = require("mithril")
var prop = require("mithril/stream")

var num = prop(1)
var doubled = num.map(function(n) {return n * 2})

m.componentが削除された

v0.2.xでは、m(component)m.component(component)のどちらをつかってもコンポーネントを作ることができました。v1.xではm(component)だけがサポートされます。

v0.2.x

// 次の行は同じ
m.component(component)
m(component)

v1.x

m(component)

config関数

v0.2.xでは、Mithrilはライフサイクルメソッドとしてconfigだけを提供していました。v1.xではVノードのライフサイクルに対して適切な粒度の制御方法を提供しています。

v0.2.x

m("div", {
    config : function(element, isInitialized) {
        // 毎回の再描画時に呼ばれる
        // isInitializedはブール値で、ノードがDOMに追加されるときにtrueになる
    }
})

v1.x

これらの新しいメソッドについては、ライフサイクルメソッドのドキュメントで紹介されています。

m("div", {
    // DOMノードが作成される前に呼ばれる
    oninit : function(vnode) { /*...*/ },
    // DOMノードが作成された後に呼ばれる
    oncreate : function(vnode) { /*...*/ },
    // DOMノードが更新前に呼ばれ、falseを返すと更新をキャンセルする
    onbeforeupdate : function(vnode, old) { /*...*/ },
    // ノードが更新された後に呼ばれる
    onupdate : function(vnode) { /*...*/ },
    // ノードが削除されたときに呼ばれる。DOMからノードを削除
    // する準備が整ったときに解決されるPromiseを返す
    onbeforeremove : function(vnode) { /*...*/ },
    // ノードが削除される前で、onbeforeremoveが完了した後に呼ばれる。
    onremove : function(vnode) { /*...*/ }
})

もしDOMエレメントにアクセス可能であれば、vnode.domプロパティを使って参照できます。


再描画時の動作の変更

Mithrilのレンダリングエンジンは基本的に半自動グローバル再描画を行いますが、いくつかのAPIと挙動が変わりました。

再描画のロックはなくなった

v0.2.xでは、Mithrilはデフォルトで一時的な描画ロジックのブロックを行っていました。m.request()は実行時に描画ループをロックし、すべての大気中のリクエストが完了したときにアンロックしていました。同様のことは手動でm.startComputation()m.endComputation()を呼び出すことで実現できました。これらのAPIと、関連する動作はv1.xで削除されました。再描画のロックはUIのバグを引き起こすことがありました。アプリケーションの一部がビューの一部の更新を停止することで、他の部分で変更が反映されないことがありました。

イベントハンドラから再描画をキャンセルする

m.mount()m.route()は、引き続きDOMイベントハンドラの実行後に再描画を行います。イベントハンドラ内からこれらの再描画をキャンセルするには、イベントオブジェクトのredrawプロパティにfalseを設定してください。

v0.2.x

m("div", {
    onclick : function(e) {
        m.redraw.strategy("none")
    }
})

v1.x

m("div", {
    onclick : function(e) {
        e.redraw = false
    }
})

同期再描画の削除

v0.2.xでは、m.redraw()にtrueを渡すことで即座にMithrilに再描画を行わせることができました。このm.redraw()メソッドを使った時の挙動は難しく、追跡が難しい問題を引き起こすことがあったので削除されました。

v0.2.x

m.redraw(true) // 即座に再描画を行って同期する

v1.x

m.redraw() // 次のrequestAnimationFrame呼び出し時に再描画が行われるように予約する

m.startComputation/m.endComputationの削除

これらの関数はさまざまなエッジケースの問題を発生させてきたため、アンチパターンと判断されました。v1.xからは除外されました。


コンポーネントのcontroller関数

v1.xのコンポーネントにはcontrollerプロパティはありません。代わりにoninitを使ってください。

v0.2.x

m.mount(document.body, {
    controller : function() {
        var ctrl = this

        ctrl.fooga = 1
    },

    view : function(ctrl) {
        return m("p", ctrl.fooga)
    }
})

v1.x

m.mount(document.body, {
    oninit : function(vnode) {
        vnode.state.fooga = 1
    },

    view : function(vnode) {
        return m("p", vnode.state.fooga)
    }
})

// OR

m.mount(document.body, {
    oninit : function(vnode) {
        var state = this  // thisはデフォルトでvnode.stateがバインドされています

        state.fooga = 1
    },

    view : function(vnode) {
        var state = this  // thisはデフォルトでvnode.stateがバインドされています

        return m("p", state.fooga)
    }
})

コンポーネントの引数

v1.xのコンポーネントはオブジェクトでなければなりません。StringNumberBooleanは子供のテキストノードとして扱われます。コンポーネント内部では、引数はvnode.attrsオブジェクトとして参照できます。

v0.2.x

var component = {
    controller : function(options) {
        // options.fooga === 1
    },

    view : function(ctrl, options) {
        // options.fooga == 1
    }
}

m("div", m.component(component, { fooga : 1 }))

v1.x

var component = {
    oninit : function(vnode) {
        // vnode.attrs.fooga === 1
    },

    view : function(vnode) {
        // vnode.attrs.fooga == 1
    }
}

m("div", m(component, { fooga : 1 }))

view()パラメータ

v0.2.xではビュー関数はcontrollerインスタンスの参照と、オプションのコンポーネントの引数を受け取りました。v1.xではcontrollerと同じように、vnodeのみを受け取ります。

v0.2.x

m.mount(document.body, {
    controller : function() {},

    view : function(ctrl, options) {
        // ...
    }
})

v1.x

m.mount(document.body, {
    oninit : function(vnode) {
        // ...
    },

    view : function(vnode) {
        // ctrlの代わりにvnode.stateを使います
        // optionsの代わりにvnode.attrsを使います
    }
})

m()にコンポーネントを渡す

v0.2.xでは、コンポーネントをm()の第二引数に直接渡すことができました。v1.xでは一貫性のために、m()呼び出しでラップする必要があります。

v0.2.x

m("div", component)

v1.x

m("div", m(component))

m.mount()m.route()にVノードを渡す

v0.2.xでは、ドキュメントには書かれてませんでしたが、m.mount(element, component)componentsの代わりにvnodesを第二引数として渡すことができました。また、m.route(element, defaultRoute, routes)routesの値としてvnodeを受け入れることができました。

v1.xではどちらの場合もコンポーネントが必要となりました。

v0.2.x

m.mount(element, m('i', 'hello'))
m.mount(element, m(Component, attrs))

m.route(element, '/', {
    '/': m('b', 'bye')
})

v1.x

m.mount(element, {view: function () {return m('i', 'hello')}})
m.mount(element, {view: function () {return m(Component, attrs)}})

m.route(element, '/', {
    '/': {view: function () {return m('b', 'bye')}}
})

m.route.mode

v0.2.xでは、"pathname""hash""search"m.route.modeに設定することでラウティングモードを変更できました。v.1.xではm.route.prefix(prefix)に変更されました。prefixとしては、#?あるいは空文字列("pathname")が使用できます。新APIはハッシュバング(#!)もサポートしており、これがデフォルトモードとなっています。このAPIはルート以外のパス名や、クエリーバング(?!)などの任意の文字列が設定できます。

v0.2.x

m.route.mode = "pathname"
m.route.mode = "search"

v1.x

m.route.prefix("")
m.route.prefix("?")

m.route()とアンカータグ

アンカータグのクリックをハンドリングしてMithrilラウターと一緒に扱う方法はv0.2.xと似ていますが、新しいライフサイクルメソッドとAPIを使います。

v0.2.x

// リンクをクリックしたときに、ナビゲーションを行うのではなく、"/path"のラウターをロードする
m("a", {
    href   : "/path",
    config : m.route
})

v1.x

// リンクをクリックしたときに、ナビゲーションを行うのではなく、"/path"のラウターをロードする
m("a", {
    href     : "/path",
    oncreate : m.route.link
})

現在のラウトの読み込み/書き込み

v0.2.xでは現在のラウトに関することはすべてm.route()メソッド経由で行っていました。v1.xではこれは2つの関数に分離されました。

v0.2.x

// 現在のラウトを取得
m.route()

// 新しいラウトを設定
m.route("/other/route")

v1.x

// 現在のラウトを取得
m.route.get()

// 新しいラウトを設定
m.route.set("/other/route")

ラウトパラメータへのアクセス

v0.2.xではラウトパラメータの読み込みはm.route.param()を使って行っていました。このAPIはv1.xでも利用可能ですが、これに加えて、ラウトパラメータはVノードのattrsプロパティを通じて取得することができます。

v0.2.x

m.route(document.body, "/booga", {
    "/:attr" : {
        controller : function() {
            m.route.param("attr") // "booga"
        },
        view : function() {
            m.route.param("attr") // "booga"
        }
    }
})

v1.x

m.route(document.body, "/booga", {
    "/:attr" : {
        oninit : function(vnode) {
            vnode.attrs.attr // "booga"
            m.route.param("attr") // "booga"
        },
        view : function(vnode) {
            vnode.attrs.attr // "booga"
            m.route.param("attr") // "booga"
        }
    }
})

クエリー文字列の組み立てとパース

v0.2.xではm.routeにぶら下がっていた関数m.route.buildQueryString()m.route.parseQueryString()を使っていました。v1.xでは、名前が変わってm配下に移動しました。

v0.2.x

var qs = m.route.buildQueryString({ a : 1 });

var obj = m.route.parseQueryString("a=1");

v1.x

var qs = m.buildQueryString({ a : 1 });

var obj = m.parseQueryString("a=1");

アンマウントを停止させる

onunloade.preventDefault()を使ってアンマウントを中断させることはもうできません。その代わりに、中断条件にマッチしたときはm.route.setを呼んでください。

v0.2.x

var Component = {
    controller: function() {
        this.onunload = function(e) {
            if (condition) e.preventDefault()
        }
    },
    view: function() {
        return m("a[href=/]", {config: m.route})
    }
}

v1.x

var Component = {
    view: function() {
        return m("a", {onclick: function() {if (!condition) m.route.set("/")}})
    }
}

コンポーネント削除時にコードを実行

コンポーネントは削除時にthis.onunloadを呼ばなくなりました。標準化されたライフサイクルフックのonremoveを使用します。

v0.2.x

var Component = {
    controller: function() {
        this.onunload = function(e) {
            // ...
        }
    },
    view: function() {
        // ...
    }
}

v1.x

var Component = {
    onremove : function() {
        // ...
    }
    view: function() {
        // ...
    }
}

m.request

m.requestが返すPromiseはm.prop()ゲッター・セッターではなくなります。これに加えて、initialValueunwrapSuccessunwrapErrorはオプションからはサポートされなくなります。

さらに、m.startComputation/m.endComputationセマンティクスはサポートされなくなります。これらはサポートされませんが、Promiseのチェーンが完了するときには、background:trueがセットされていない時は再描画が実行されます。

v0.2.x

var data = m.request({
    method: "GET",
    url: "https://api.github.com/",
    initialValue: [],
})

setTimeout(function() {
    console.log(data())
}, 1000)

v1.x

var data = []
m.request({
    method: "GET",
    url: "https://api.github.com/",
})
.then(function (responseBody) {
    data = responseBody
})

setTimeout(function() {
    console.log(data) // note: これはゲッター・セッターではありません
}, 1000)

これに加えて、extractオプションがm.requestに渡されると、指定された関数の返り値がPromiseの解決にそのまま使われ、deserializeコールバックは無視されます。


m.deferredが削除された

v0.2.xでは、m.deferredという名前でMithril自身が提供する非同期の契約オブジェクトが使われてきました。また、これはm.requestからも使用されていました。v1.xではこれの代わりにPromiseが使われます。非サポートの環境のためにpolyfillを使った実装になっています。m.deferredを使用していたところではPromiseを使用してください。

v0.2.x

var greetAsync = function() {
    var deferred = m.deferred()
    setTimeout(function() {
        deferred.resolve("hello")
    }, 1000)
    return deferred.promise
}

greetAsync()
    .then(function(value) {return value + " world"})
    .then(function(value) {console.log(value)}) // 1秒後に "hello world" のログ出力

v1.x

var greetAsync = function() {
    return new Promise(function(resolve){
        setTimeout(function() {
            resolve("hello")
        }, 1000)
    })
}

greetAsync()
    .then(function(value) {return value + " world"})
    .then(function(value) {console.log(value)}) // 1秒後に "hello world" のログ出力

m.syncが削除された

v1.xでは標準準拠のPromiseが使われるようになったため、m.syncは削除されました。Promise.allを使用してください。

v0.2.x

m.sync([
    m.request({ method: 'GET', url: 'https://api.github.com/users/lhorie' }),
    m.request({ method: 'GET', url: 'https://api.github.com/users/isiahmeadows' }),
])
.then(function (users) {
    console.log("コントリビュータ:", users[0].name, "and", users[1].name)
})

v1.x

Promise.all([
    m.request({ method: 'GET', url: 'https://api.github.com/users/lhorie' }),
    m.request({ method: 'GET', url: 'https://api.github.com/users/isiahmeadows' }),
])
.then(function (users) {
    console.log("コントリビュータ:", users[0].name, "and", users[1].name)
})

v0.2.xでは、xlink名前空間は属性の名前空間として唯一サポートされていて、特別な場合だけサポートしていました。現在は名前空間のパースは完全にサポートされました。名前空間の属性は、その名前空間の中で明示的に定義されなければなりません。

v0.2.x

m("svg",
    // `href`属性は自動で名前空間が付与される
    m("image[href='image.gif']")
)

v1.x

m("svg",
    // `href`属性にユーザー指定の名前空間を付与する
    m("image[xlink:href='image.gif']")
)

ビューのネストされた配列

配列はフラグメントを表現するのに使われるようになりました。これはv1.xの仮想DOMの主要な構造です。v0.2.xではネストされた配列は差分検知のためにフラット化され、1つのリストにされていました。v1.xでは配列構造をそのまま維持します。別の配列の子要素が、京大の要素として扱われることはありません。


vnode同値性チェック

もしvnodeが前回の描画時のものと厳格に一致していたときは、v1.xはその部分のサブツリーのチェックや、ライフサイクルメソッドの呼び出しをスキップします。コンポーネントのドキュメントにこの問題に関する詳細情報が含まれています。


License: MIT. © Leo Horie.