v1.x からの移行
v2.x は、ほぼすべての API において v1.x と互換性がありますが、いくつかの非互換な変更点があります。
vnode.state
への代入
v1.x では、vnode.state
を自由に操作し、任意の値を代入できました。v2.x では、vnode.state
への直接的な代入はエラーとなります。移行方法はケースバイケースで異なりますが、多くの場合、vnode.state
への参照を vnode.state.foo
に変更し、foo
に適切な名前(例えば、カウンターの現在の値であれば count
など)を選択するだけで済みます。
v1.x
var Counter = {
oninit: function (vnode) {
vnode.state = 0;
},
view: function (vnode) {
return m('.counter', [
m(
'button',
{
onclick: function () {
vnode.state--;
},
},
'-'
),
vnode.state,
m(
'button',
{
onclick: function () {
vnode.state++;
},
},
'+'
),
]);
},
};
v2.x
var Counter = {
oninit: function (vnode) {
vnode.state.count = 0;
},
view: function (vnode) {
return m('.counter', [
m(
'button',
{
onclick: function () {
vnode.state.count--;
},
},
'-'
),
vnode.state.count,
m(
'button',
{
onclick: function () {
vnode.state.count++;
},
},
'+'
),
]);
},
};
v1.0 が最初にリリースされた当時、クラスコンポーネントとクロージャコンポーネントは存在しなかったため、必要な情報を vnode.tag
から取得していました。この実装の詳細が、そのような操作を可能にし、一部の開発者はそれに依存するようになりました。また、ドキュメントの一部でも、それが可能であることが示唆されていました。現在、状況は異なり、状態への参照が 2 つではなく 1 つになるため、実装の観点から管理が容易になります。
ルートアンカーの変更
v1.x では、oncreate: m.route.link
を使用し、リンクが変更される可能性がある場合には onupdate: m.route.link
も使用していました。これらはそれぞれ、ルーティング可能な vnode のライフサイクルフックとして機能していました。v2.x では、m.route.Link
コンポーネントを使用するようになりました。m("a", ...)
以外を使用していた場合は、selector:
属性でセレクタを指定できます。オプションは options:
で指定できます。disabled:
で無効化できます。また、href:
(必須) を含むその他の属性はインラインで指定できます。selector:
自体には、m
の最初の引数として有効な任意のセレクタを含めることができ、属性 [href=...]
および [disabled]
は、通常のオプションと同様にセレクタで指定できます。
v1.x
m('a', {
href: '/path',
oncreate: m.route.link,
});
m('button', {
href: '/path',
oncreate: m.route.link,
});
m('button.btn[href=/path]', {
oncreate: m.route.link,
});
v2.x
m(m.route.Link, {
href: '/path',
});
m(m.route.Link, {
selector: 'button',
href: '/path',
});
m(m.route.Link, {
selector: 'button.btn[href=/path]',
});
m.request
エラーの変更
v1.x では、m.request
は JSON 呼び出しからエラーを解析し、その結果として得られた解析済みのオブジェクトのプロパティをレスポンスに割り当てていました。したがって、ステータス 403 で本文が {"code": "backoff", "timeout": 1000}
のレスポンスを受信した場合、エラーには err.code = "backoff"
と err.timeout = 1000
という 2 つのプロパティが追加されていました。
v2.x では、レスポンスは代わりに結果の response
プロパティに割り当てられ、code
プロパティには結果のステータスコードが含まれます。したがって、ステータス 403 で {"code": "backoff", "timeout": 1000}
の本文を含むレスポンスを受信した場合、エラーには err.response = {code: "backoff", timeout: 1000}
と err.code = 403
の 2 つのプロパティが割り当てられます。
m.withAttr
の削除
v1.x では、イベントリスナーは oninput: m.withAttr("value", func)
のように使用できました。v2.x では、イベントのターゲットから直接値を読み取るだけです。これはストリームとうまく連携しましたが、m.withAttr("value", stream)
のイディオムは m.withAttr("value", prop)
ほど一般的ではなかったため、m.withAttr
はその有用性を大きく損ない、削除されることになりました。
v1.x
var value = '';
// In your view
m('input[type=text]', {
value: value(),
oninput: m.withAttr('value', function (v) {
value = v;
}),
});
// OR
var value = m.stream('');
// In your view
m('input[type=text]', {
value: value(),
oninput: m.withAttr('value', value),
});
v2.x
var value = '';
// In your view
m('input[type=text]', {
value: value,
oninput: function (ev) {
value = ev.target.value;
},
});
// OR
var value = m.stream('');
// In your view
m('input[type=text]', {
value: value(),
oninput: function (ev) {
value(ev.target.value);
},
});
m.route.prefix
v1.x では、m.route.prefix
は m.route.prefix(prefix)
を介して呼び出される関数でした。現在は、m.route.prefix = prefix
を介して設定するプロパティです。
v1.x
m.route.prefix('/root');
v2.x
m.route.prefix = '/root';
m.request
/m.jsonp
の params と body
data
と useBody
は、URL に補間され、リクエストに追加されるクエリパラメータである params
と、基になる XHR で送信される本文である body
にリファクタリングされました。これにより、送信される実際のリクエストをより詳細に制御できるようになり、POST
リクエストでクエリパラメータを補間したり、本文付きの GET
リクエストを作成したりすることが可能になります。
m.jsonp
には意味のある「body」がないため、params
のみを使用します。したがって、data
を params
に名前変更するだけで、そのメソッドには十分です。
v1.x
m.request('https://example.com/api/user/:id', {
method: 'GET',
data: { id: user.id },
});
m.request('https://example.com/api/user/create', {
method: 'POST',
data: userData,
});
v2.x
m.request('https://example.com/api/user/:id', {
method: 'GET',
params: { id: user.id },
});
m.request('https://example.com/api/user/create', {
method: 'POST',
body: userData,
});
パステンプレート
v1.x では、3 つの別々のパステンプレート構文がありましたが、それらは似ていたものの、2 つの別々に設計された構文と 3 つの異なる実装がありました。これはかなりアドホックな方法で定義されており、パラメータは一般的にエスケープされていませんでした。現在、すべてが :key
の場合はエンコードされ、:key...
の場合は raw になります。予期せぬエンコードが発生する場合は、:path...
を使用してください。非常にシンプルです。
具体的には、各メソッドにどのように影響するかを次に示します。
m.request
と m.jsonp
の URL、m.route.set
のパス
v2.x のパスコンポーネントは、補間時に自動的にエスケープされます。m.route.set("/user/:name/photos/:id", {name: user.name, id: user.id})
を呼び出すとします。以前は、user
が {name: "a/b", id: "c/d"}
の場合、これはルートを /user/a/b/photos/c/d
に設定していましたが、現在は /user/a%2Fb/photos/c%2Fd
に設定します。キーをエスケープせずに意図的に補間する場合は、代わりに :key...
を使用します。
v2.x のキーには、.
または -
のインスタンスを含めることはできません。v1.x では、/
以外のものを含めることができました。
/api/search?q=:query
のようなインラインクエリ文字列の補間は、v2.x では実行されません。代わりに、適切なキー名を使用して params
を介して渡してください。クエリ文字列で直接指定するのではなく。
m.route
のルートパターン
:key...
形式のパスキーは、v1.x では URL デコードされたものを返していましたが、v2.x では raw URL を返します。
以前は、:key.md
のようなものが誤って受け入れられ、結果のパラメータの値が keymd: "..."
に設定されていました。これは当てはまらなくなったため、.md
は名前ではなく、パターンの一部として扱われます。
ライフサイクルフックの呼び出し順序
v1.x では、コンポーネント vnode の属性ライフサイクルフックは、すべての場合においてコンポーネント自身のライフサイクルフックの前に呼び出されていました。v2.x では、これは onbeforeupdate
の場合にのみ当てはまります。したがって、必要に応じてコードを調整してください。
v1.x
var Comp = {
oncreate: function () {
console.log('Component oncreate');
},
view: function () {
return m('div');
},
};
m.mount(document.body, {
view: function () {
return m(Comp, {
oncreate: function () {
console.log('Attrs oncreate');
},
});
},
});
// ログ:
// Attrs oncreate
// Component oncreate
v2.x
var Comp = {
oncreate: function () {
console.log('Component oncreate');
},
view: function () {
return m('div');
},
};
m.mount(document.body, {
view: function () {
return m(Comp, {
oncreate: function () {
console.log('Attrs oncreate');
},
});
},
});
// ログ:
// Component oncreate
// Attrs oncreate
m.redraw
の同期性
v2.x の m.redraw()
は常に非同期的に動作します。現在リドローが実行中でない場合に限り、m.redraw.sync()
を使用して同期的な再描画を明示的に要求できます。
セレクター属性の優先順位
v1.x では、セレクター属性は属性オブジェクトで指定された属性よりも優先されていました。たとえば、m("[a=b]", {a: "c"}).attrs
は {a: "b"}
を返しました。
v2.x では、属性オブジェクトで指定された属性が、セレクター属性よりも優先されます。たとえば、m("[a=b]", {a: "c"}).attrs
は {a: "c"}
を返します。
技術的には、これは v0.2.x の動作に戻ったことに注意してください。
子の正規化
v1.x では、コンポーネント vnode の子は、他の vnode と同様に正規化されていました。v2.x では、これはもう当てはまらないため、それに応じて計画する必要があります。これは、レンダリング時に行われる正規化には影響しません。
m.request
ヘッダー
v1.x では、これらの 2 つのヘッダーをすべての非 GET
リクエストに設定していましたが、useBody
が true
(デフォルト) に設定され、リストされている他の条件が満たされている場合にのみ設定されていました。
- JSON 本文を含むリクエストの場合は
Content-Type: application/json; charset=utf-8
- JSON レスポンスを期待するリクエストの場合は
Accept: application/json, text/*
v2.x では、Mithril.js は、JSON 本文を含むすべてのリクエストに対して最初のヘッダーを設定します。これは != null
の場合に適用され、それ以外の場合はデフォルトで省略されます。また、これは、GET
リクエストを含む、どのメソッドが選択されているかに関係なく行われます。
2 つのヘッダーのうち最初のヘッダーである Content-Type
は、指定されたコンテンツタイプが CORS セーフリストリクエストヘッダーではないため、CORS プリフライトリクエストをトリガーし、サーバーで CORS がどのように構成されているかに応じて、新しいエラーが発生する可能性があります。この問題が発生した場合は、headers: {"Content-Type": "text/plain"}
を指定して、該当するヘッダーを上書きする必要が生じる可能性があります。(Accept
ヘッダーは何もトリガーしないため、上書きする必要はありません。)
Fetch 仕様で CORS プリフライトチェックを回避できるコンテンツタイプは、application/x-www-form-urlencoded
、multipart/form-data
、および text/plain
のみです。それ以外は許可されておらず、意図的に JSON を許可していません。
ルートのハッシュ文字列のクエリパラメータ
v1.x では、クエリ文字列とハッシュ文字列の両方でルートのクエリパラメータを指定できました。したがって、m.route.set("/route?foo=1&bar=2")
、m.route.set("/route?foo=1#bar=2")
、および m.route.set("/route#foo=1&bar=2")
はすべて同等であり、それらから抽出された属性は {foo: "1", bar: "2"}
になっていました。
v2.x では、ハッシュ文字列の内容は無視されますが、保持されるようになりました。したがって、それぞれから抽出された属性は次のようになります。
m.route.set("/route?foo=1&bar=2")
→{foo: "1", bar: "2"}
m.route.set("/route?foo=1#bar=2")
→{foo: "1"}
m.route.set("/route#foo=1&bar=2")
→{}
これを行う理由は、https://example.com/#!/route#key
のような URL が、URL 仕様 では技術的に無効であり、それに先行する RFC でも無効であったためです。そして、HTML 仕様の癖によってのみ許可されています。(HTML 仕様は、仕様に従う場合は、最初から ID とロケーションフラグメントが有効な URL フラグメントである必要がありました。)
つまり、無効な URL の使用はやめましょう!
key
v1.x では、key 付きの vnode と key なしの vnode を自由に混在させることができました。最初のノードに key がある場合、key 付きの差分が実行され、すべての要素に key があると想定し、穴を無視するだけでした。それ以外の場合は、反復的な差分が実行され、ノードに key がある場合、タグなどがチェックされるのと同時に変更されていないことが確認されていました。
v2.x では、フラグメントと要素の両方の子リストは、すべて key 付きであるか、すべて key なしである必要があります。穴も、このチェックの目的では key なしと見なされます - 無視されなくなりました。
この問題を回避するには、[m("div", {key: whatever})]
のように、単一の vnode を含むフラグメントというイディオムを使用してください。
m.version
の削除
一般的にはほとんど役に立たず、いつでも自分で追加することができます。利用可能な機能を把握するには、機能検出を優先する必要があります。また、v2.x API は、これをより適切に実現するように設計されています。