パスの取り扱い
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: ...}}) のようにパラメータを明示的に指定する場合にのみ注意する必要があります。