パスの取り扱い
m.route
と m.request
は、それぞれパスという概念を持っています。これは、ルーティング先またはフェッチ元の URL を生成するために使用されます。
パスの種類
パスには、大きく分けて、生のパスとパラメータ付きパスの 2 種類があります。
- 生のパスは、URL として直接使用される文字列です。置換や分割は行われず、パラメータは末尾に追加され、正規化されます。
- パラメータ付きパスを使用すると、パスに値を挿入できます。URL インジェクション対策として、利便性と安全性を考慮してデフォルトでエスケープされます。
m.request
の場合、これらはほぼすべての URL になりますが、m.route
の場合、スキームやドメインを含まない絶対 URL パス名のみになります。
パスパラメータ
パスパラメータは、以下の 2 つの形式で記述できます。
:foo
-params.foo
の値を URL に挿入し、その値をエスケープします。:foo...
-params.foo
の値を生のパスとして URL に挿入し、エスケープしません。
params
オブジェクトは、m.route.set(path, params)
または m.request({url, params})
に渡される params
オブジェクトです。
m.route(root, defaultRoute, routes)
を介してルートを受信する場合、これらのパラメータを使用してルートから値を抽出できます。これは基本的にパスの生成と同じように機能しますが、逆方向の処理になります。
// 単一のアイテムを編集
m.route(document.body, '/edit/1', {
'/edit/:id': {
view: function () {
return [m(Menu), m('h1', 'Editing user ' + m.route.param('id'))];
},
},
});
// パスで識別されるアイテムを編集
m.route(document.body, '/edit/pictures/image.jpg', {
'/edit/:file...': {
view: function () {
return [m(Menu), m('h1', 'Editing file ' + m.route.param('file'))];
},
},
});
最初の例では、デフォルトルートに移動すると仮定すると、m.route.param("id")
は "1"
として読み取られ、m.route.param("file")
は pictures/image.jpg
として読み取られます。
パスパラメータは、/
、-
、または .
で区切ることができます。これにより、動的なパスセグメントを持つことができ、単なるパス名よりも柔軟になります。たとえば、ファイル拡張子に基づいて編集する場合は "/edit/:name.:ext"
のようなルート、ローカライズされたルートの場合は "/:lang-:region/view"
のようなルートと照合できます。
パスパラメータは貪欲にマッチします。宣言されたルート "/edit/:name.:ext"
が与えられた場合、/edit/file.test.png
に移動すると、抽出されるパラメータは {name: "file.test", ext: "png"}
になり、{name: "file", ext: "test.png"}
にはなりません。同様に、"/route/:path.../view/:child..."
が与えられた場合、/route/foo/view/bar/view/baz
に移動すると、抽出されるパラメータは {path: "foo/view/bar", child: "baz"}
になります。
パラメータの正規化
パス名に埋め込まれたパスパラメータは、クエリ文字列から省略されます。これは、パス名を読みやすくするためです。たとえば、これは GET /api/user/1/connections?sort=name-asc
のサーバーリクエストを送信し、URL 文字列内の重複する id=1
が省略されます。
m.request({
url: 'https://example.com/api/user/:userID/connections',
params: {
userID: 1,
sort: 'name-asc',
},
});
また、クエリ文字列自体でパラメータを明示的に指定することもできます。たとえば、これは上記と同等です。
m.request({
url: 'https://example.com/api/user/:userID/connections?sort=name-asc',
params: {
userID: 1,
},
});
そしてもちろん、組み合わせることもできます。これは、GET /api/user/1/connections?sort=name-asc&first=10
へのリクエストを送信します。
m.request({
url: 'https://example.com/api/user/:userID/connections?sort=name-asc',
params: {
userID: 1,
first: 10,
},
});
この機能は、ルートマッチングにも適用されます。明示的なクエリ文字列を持つルートとのマッチングも可能です。利便性のために、一致したパラメータは保持されるため、vnode のパラメータまたは m.route.param
を介してアクセスできます。ただし、一般的にはパスを優先することが推奨されます。特定のファイルタイプに対してわずかに異なるビューを生成する必要がある場合に役立つことがありますが、論理的にはクエリパラメータに近いものであり、完全に別のページではありません。
// 注:一般的に、クエリ文字列ではなくパスをルート定義に使用することが推奨されます。
m.route(document.body, '/edit/1', {
'/edit?type=image': {
view: function () {
return [m(Menu), m('h1', 'Editing photo')];
},
},
'/edit': {
view: function () {
return [m(Menu), m('h1', 'Editing ' + m.route.param('type'))];
},
},
});
クエリパラメータは暗黙的に処理されます。それらを受け入れるために名前を付ける必要はありません。"/edit?type=image"
のように既存の値に基づいて一致させることができますが、値を受け入れるために "/edit?type=:type"
を使用する必要はありません。実際、Mithril.js はそれを m.route.param("type") === ":type"
とリテラルとしてマッチさせようとしていると見なすため、避けるべきです。クエリパラメータを読み取るには、m.route.param("key")
またはルートコンポーネント属性を使用します。
パスの正規化
解析されたパスは常に、重複するパラメータと余分なスラッシュが削除され、常にスラッシュで始まります。これらの小さな違いは、ルーティングとパスの処理を複雑にする原因となります。Mithril.js は内部的にルーティング用にパスを正規化しますが、現在の正規化されたルートを直接公開しません。(m.parsePathname(m.route.get()).path
を介して計算できます。)
マッチング中にパラメータが重複排除される場合、クエリ文字列内のパラメータはパス名内のパラメータよりも優先され、URL の末尾に近いパラメータは URL の先頭に近いパラメータよりも優先されます。
パスのエスケープ
特定の文字をそのまま使用したい場合は、エスケープ処理が必要です。encodeURIComponent
はこれらの文字(その他)をエンコードし、パラメータを置換してクエリパラメータを追加すると、必要に応じてエンコードされます。Mithril.js が解釈するものは次のとおりです。
:
=%3A
/
=%2F
(パス内でのみ必須)%
=%25
?
=%3F
(パス内でのみ必須)#
=%23
もちろん、URL 仕様に従ってエスケープする必要があるものもあります。たとえば、スペースなどがあります。ただし、encodeURIComponent
はそれを実行し、Mithril.js はパラメータを置換するときに暗黙的にそれを使用します。したがって、m.request("https://example.com/api/user/User%20Name/:field", {params: {field: ...}})
のようにパラメータを明示的に指定する場合にのみ注意する必要があります。