route(root, defaultRoute, routes)
Description
アプリケーション内の「ページ」間のナビゲーションを制御します。
var Home = {
view: function () {
return 'Welcome';
},
};
m.route(document.body, '/home', {
'/home': Home, // `https://localhost/#!/home` を定義
});
1つのアプリケーション内で m.route
は一度だけ呼び出すことができます。
Signature
m.route(root, defaultRoute, routes)
引数 | 型 | 必須 | 説明 |
---|---|---|---|
root | Element | はい | 子要素の親ノードとなる DOM 要素 |
defaultRoute | String | はい | 現在の URL がどのルートにも一致しない場合にリダイレクトするルート。これは初期ルートではありません。初期ルートはアドレスバーの URL によって決定されます。 |
routes | Object<String,Component|RouteResolver> | はい | キーがルート文字列、値がコンポーネントまたは RouteResolver であるオブジェクト |
戻り値 | undefined を返します |
静的メンバー
m.route.set
指定されたルートにリダイレクトします。一致するルートが見つからない場合は、デフォルトルートにリダイレクトします。すべてのマウントポイントで非同期に再描画をトリガーします。
m.route.set(path, params, options)
引数 | 型 | 必須 | 説明 |
---|---|---|---|
path | String | はい | パス名(プレフィックスなし)。パスには、params の値で補間されるパラメータを含めることができます。 |
params | Object | いいえ | ルーティングパラメータ。path にルーティングパラメータのスロットがある場合、このオブジェクトのプロパティがパス文字列に埋め込まれます。 |
options.replace | Boolean | いいえ | 新しい履歴エントリを作成するか、現在のエントリを置き換えるかを指定します。デフォルトは false です。 |
options.state | Object | いいえ | 基になる history.pushState / history.replaceState 呼び出しに渡す state オブジェクト。この state オブジェクトは history.state プロパティで使用可能になり、ルーティングパラメータオブジェクトにマージされます。このオプションは pushState API を使用する場合にのみ有効で、ルーターが hashchange モードにフォールバックする場合は無視されることに注意してください(つまり、pushState API が利用できない場合)。 |
options.title | String | いいえ | 基になる history.pushState / history.replaceState 呼び出しに渡す title 文字列。 |
戻り値 | undefined を返します |
.set
を params
と一緒に使用する場合は、対応するルートを定義する必要があることに注意してください。
var Article = {
view: function (vnode) {
return 'This is article ' + vnode.attrs.articleid;
},
};
m.route(document.body, {
'/article/:articleid': Article,
});
m.route.set('/article/:articleid', { articleid: 1 });
m.route.get
プレフィックスなしで、最後に完全に解決されたルーティングパスを返します。非同期ルートが解決を待機している間、ロケーションバーに表示されるパスとは異なる場合があります。
path = m.route.get()
引数 | 型 | 必須 | 説明 |
---|---|---|---|
戻り値 | String | 最後に完全に解決されたパスを返します |
m.route.prefix
ルーターのプレフィックスを定義します。ルーターのプレフィックスは、ルーターが使用する基盤となる戦略を決定する URL の一部です。
m.route.prefix = prefix
引数 | 型 | 必須 | 説明 |
---|---|---|---|
prefix | String | はい | Mithril が使用する基盤となるルーティング戦略を制御するプレフィックス。 |
これは単純なプロパティなので、読み書き可能です。
m.route.Link
このコンポーネントは、動的なルーティングリンクを作成します。主な機能は、ローカルな href
を持つ a
リンクを生成し、ルートプレフィックスを考慮して変換することです。
m(m.route.Link, { href: '/foo' }, 'foo');
// m.route.prefix がデフォルトから変更されていない限り、以下のようにレンダリングされます。
// <a href="#!/foo">foo</a>
リンクは以下の特別な属性を受け入れます。
selector
は、m
の最初の引数として渡されるものです。a
要素以外を含む、任意のセレクターが有効です。params
とoptions
は、m.route.set
で定義されているものと同じ引数です。disabled
がtrue
の場合、ルーティングの動作とバインドされたonclick
ハンドラが無効になり、アクセシビリティのヒントとしてdata-disabled="true"
属性が付与されます。要素がa
の場合、href
は削除されます。
ルーティング処理は、イベント処理 API を使用して阻止することはできません。代わりに disabled
を使用してください。
m(
m.route.Link,
{
href: '/foo',
selector: 'button.large',
disabled: true,
params: { key: 'value' },
options: { replace: true },
},
'link name'
);
// 以下のようにレンダリングされます。
// <button disabled aria-disabled="true" class="large">link name</button>
vnode = m(m.route.Link, attributes, children)
引数 | 型 | 必須 | 説明 |
---|---|---|---|
attributes.href | Object | はい | ナビゲート先のターゲットルート。 |
attributes.disabled | Boolean | いいえ | 要素へのアクセスを無効にします。 |
attributes.selector | String|Object|Function | いいえ | m のセレクター。デフォルトは "a" 。 |
attributes.options | Object | いいえ | m.route.set に渡される options を設定します。 |
attributes.params | Object | いいえ | m.route.set に渡される params を設定します。 |
attributes | Object | いいえ | m に転送されるその他の属性。 |
children | Array<Vnode>|String|Number|Boolean | いいえ | このリンクの子 vnode。 |
戻り値 | Vnode | vnode。 |
m.route.param
最後に完全に解決されたルートからルートパラメータを取得します。ルートパラメータは、キーと値の組み合わせです。ルートパラメータは、いくつかの異なる場所から取得されます。
- ルートの補間(例:ルートが
/users/:id
で、/users/1
に解決される場合、ルートパラメータにはキーid
と値"1"
があります) - ルーターのクエリ文字列(例:パスが
/users?page=1
の場合、ルートパラメータにはキーpage
と値"1"
があります) history.state
(例:history.state
が{foo: "bar"}
の場合、ルートパラメータにはキーfoo
と値"bar"
があります)
value = m.route.param(key)
引数 | 型 | 必須 | 説明 |
---|---|---|---|
key | String | いいえ | ルートパラメータ名(例:ルート /users/:id の id 、またはパス /users/1?page=3 の page 、または history.state のキー) |
戻り値 | String|Object | 指定されたキーの値を返します。キーが指定されていない場合は、すべての補間キーを含むオブジェクトを返します |
RouteResolver の onmatch
関数では、新しいルートはまだ完全に解決されておらず、m.route.param()
は、もしあれば、前のルートのパラメータを返すことに注意してください。onmatch
は、新しいルートのパラメータを引数として受け取ります。
m.route.SKIP
ルートリゾルバーの onmatch
から返され、次のルートにスキップするための特別な値。
RouteResolver
RouteResolver は、onmatch
メソッド、render
メソッド、またはその両方を含む、コンポーネントではないオブジェクトです。両方のメソッドはオプションですが、少なくとも1つは存在する必要があります。
オブジェクトがコンポーネントとして認識できる場合(view
メソッドの存在、または function
/class
であることによって)、onmatch
メソッドまたは render
メソッドがあっても、コンポーネントとして扱われます。RouteResolver はコンポーネントではないため、ライフサイクルメソッドはありません。
経験則として、RouteResolver は m.route
呼び出しと同じファイルに配置し、コンポーネントの定義は独自のモジュールに配置すべきです。
routeResolver = {onmatch, render}
コンポーネントを使用する場合、コンポーネントが Home
であると仮定すると、これらをこのルートリゾルバーの特別な糖衣構文と考えることができます。
var routeResolver = {
onmatch: function () {
return Home;
},
render: function (vnode) {
return [vnode];
},
};
routeResolver.onmatch
onmatch
フックは、ルーターがレンダリングするコンポーネントを決定する必要があるときに呼び出されます。ルーターパスが変更されるごとに1回呼び出されますが、同じパスにおける後続の再描画では呼び出されません。コンポーネントの初期化前にロジックを実行するために使用できます(例:認証ロジック、データプリロード、リダイレクト分析トラッキングなど)。
このメソッドを使用すると、非同期的にレンダリングされるコンポーネントを定義できます。コンポーネントを非同期的にレンダリングするには、コンポーネントに解決される Promise を返します。
onmatch
の詳細については、高度なコンポーネント解決セクションを参照してください。
routeResolver.onmatch(args, requestedPath, route)
引数 | 型 | 説明 |
---|---|---|
args | Object | ルーティングパラメータ |
requestedPath | String | 最後のルーティングアクションによって要求されたルーターパス。補間されたルーティングパラメータ値を含みますが、プレフィックスは含みません。onmatch が呼び出されると、このパスの解決処理は完了しておらず、m.route.get() は依然として前のパスを返します。 |
route | String | 最後のルーティングアクションによって要求されたルーターパス。補間されたルーティングパラメータ値を除きます。 |
戻り値 | Component|\Promise<Component>|undefined | コンポーネントまたはコンポーネントに解決される Promise を返します |
onmatch
がコンポーネントまたはコンポーネントに解決される Promise を返す場合、このコンポーネントは RouteResolver の render
メソッドの最初の引数の vnode.tag
として使用されます。それ以外の場合、vnode.tag
は "div"
に設定されます。同様に、onmatch
メソッドが省略された場合、vnode.tag
も "div"
になります。
onmatch
が拒否された Promise を返す場合、ルーターは defaultRoute
にリダイレクトします。Promise チェーンで .catch
を呼び出すことで、この動作をオーバーライドできます。
routeResolver.render
render
メソッドは、一致するルートの再描画ごとに呼び出されます。コンポーネントの view
メソッドに似ており、コンポーネントの合成を簡素化するために存在します。また、Mithril.js がサブツリー全体を置き換えるという通常の動作から逸脱することもできます。
vnode = routeResolver.render(vnode)
引数 | 型 | 説明 |
---|---|---|
vnode | Object | 属性オブジェクトにルーティングパラメータを含むvnode。onmatch がコンポーネントまたはコンポーネントに解決される Promise を返さない場合、vnode の tag フィールドはデフォルトで "div" になります |
vnode.attrs | Object | URL パラメータ値のマップ |
戻り値 | Array<Vnode>|Vnode | レンダリングされるvnode |
vnode
パラメータは、m(Component, m.route.param())
と同じです。ここで、Component
はルートの解決済みコンポーネント(routeResolver.onmatch
の後)であり、m.route.param()
はこちらで説明されているとおりです。このメソッドを省略した場合、デフォルトの戻り値は [vnode]
であり、key パラメータを使用できるようにフラグメントでラップされます。:key
パラメータと組み合わせると、単一要素のキー付きフラグメントになります。これは、[m(Component, {key: m.route.param("key"), ...})]
のようにレンダリングされるためです。
仕組み
ルーティングは、シングルページアプリケーション(SPA)を構築するためのシステムです。つまり、ブラウザを完全にリフレッシュすることなく、「ページ」から別のページに遷移できるアプリケーションです。
各ページを個別にブックマークする機能と、ブラウザの履歴メカニズムを介してアプリケーション内をナビゲートする機能を維持しながら、シームレスなナビゲーションを可能にします。
ページをリフレッシュしないルーティングは、history.pushState
API によって部分的に実現されます。この API を使用すると、ページがロードされた後にブラウザに表示される URL をプログラムで変更できますが、コールド状態(例:新しいタブ)から特定の URL にナビゲートした場合に、適切なマークアップがレンダリングされるようにするのは、アプリケーション開発者の責任です。
ルーティング戦略
ルーティング戦略は、ライブラリが実際にルーティングをどのように実装するかを規定します。SPA ルーティングシステムを実装するために使用できる一般的な戦略は3つあり、それぞれに異なる注意点があります。
m.route.prefix = '#!'
(デフォルト) – URL のフラグメント識別子(ハッシュとも呼ばれます)の部分を使用します。この戦略を使用する URL は、通常https://localhost/#!/page1
のようになります。m.route.prefix = '?'
– クエリ文字列を使用します。この戦略を使用する URL は、通常https://localhost/?/page1
の形式になります。m.route.prefix = ''
– パス名を使用します。この戦略を使用する URL は、通常https://localhost/page1
のようになります。
ハッシュ戦略を使用すると、history.pushState
をサポートしていないブラウザでも動作することが保証されます。これは、onhashchange
の使用にフォールバックできるためです。ハッシュを完全にローカルに保持する場合は、この戦略を使用してください。
クエリ文字列戦略を使用すると、サーバー側での検出が可能ですが、通常のパスとしては表示されません。アンカー付きリンクをサーバー側でサポートし、潜在的に検出したいものの、パス名戦略をサポートするために必要な変更を加えることができない場合(Apache を使用していて、.htaccess
を変更できない場合など)は、この戦略を使用してください。
パス名戦略は、最もクリーンな URL を生成しますが、アプリケーションがルーティングできるすべての URL からシングルページアプリケーションコードを提供するようにサーバーを設定する必要があります。よりクリーンな URL が必要な場合は、この戦略を使用してください。
ハッシュ戦略を使用するシングルページアプリケーションは、ハッシュをアンカーへのリンクの目的ではなく、ルーティングメカニズムとして使用していることを示すために、ハッシュの後に感嘆符を付けることが一般的です。#!
文字列は、_ハッシュバン_として知られています。
デフォルトの戦略ではハッシュバンを使用します。
一般的な使用法
通常、ルートをマッピングするために、いくつかのコンポーネントを作成する必要があります。
var Home = {
view: function () {
return [m(Menu), m('h1', 'Home')];
},
};
var Page1 = {
view: function () {
return [m(Menu), m('h1', 'Page 1')];
},
};
上記の例では、Home
と Page1
の2つのコンポーネントがあります。それぞれにメニューといくつかのテキストが含まれています。メニュー自体は、繰り返しを避けるためにコンポーネントとして定義されています。
var Menu = {
view: function () {
return m('nav', [
m(m.route.Link, { href: '/' }, 'Home'),
m(m.route.Link, { href: '/page1' }, 'Page 1'),
]);
},
};
これで、ルートを定義し、コンポーネントをそれらにマッピングできます。
m.route(document.body, '/', {
'/': Home,
'/page1': Page1,
});
ここでは、/
と /page1
の2つのルートを指定します。これにより、ユーザーが各 URL にナビゲートすると、それぞれのコンポーネントがレンダリングされます。
異なるルートへのナビゲート
上記の例では、Menu
コンポーネントに2つの m.route.Link
があります。これにより、要素(デフォルトでは <a>
)が作成され、ユーザーがクリックすると、別のルートにナビゲートするように設定されます。リモートにナビゲートするのではなく、ローカルにナビゲートします。
m.route.set(route)
を使用して、プログラムでナビゲートすることもできます。たとえば、m.route.set("/page1")
のようにします。
ルート間をナビゲートする場合、ルータープレフィックスは自動的に処理されます。つまり、Mithril.js ルートをリンクする場合は、m.route.set
と m.route.Link
の両方で、ハッシュバン #!
(または m.route.prefix
に設定したプレフィックス)を省略します。
コンポーネント間を遷移する場合、サブツリー全体が置き換えられることに注意してください。サブツリーをパッチするだけなら、render
メソッドを持つルートリゾルバーを使用してください。
ルーティングパラメータ
場合によっては、変数 ID または同様のデータをルートに表示したいが、可能なすべての ID に対して個別のルートを明示的に指定したくない場合があります。これを実現するために、Mithril.js はパラメータ化されたルートをサポートしています。
var Edit = {
view: function (vnode) {
return [m(Menu), m('h1', 'Editing ' + vnode.attrs.id)];
},
};
m.route(document.body, '/edit/1', {
'/edit/:id': Edit,
});
上記の例では、ルート /edit/:id
を定義しました。これにより、/edit/
で始まり、その後に何らかのデータが続く URL(例:/edit/1
、edit/234
など)に一致する動的ルートが作成されます。次に、id
値はコンポーネントの vnode の属性(vnode.attrs.id
)としてマッピングされます。
ルートに複数の引数を含めることができます。たとえば、/edit/:projectID/:userID
は、コンポーネントの vnode 属性オブジェクトにプロパティ projectID
と userID
を生成します。
Key パラメータ
ユーザーがパラメータ化されたルートから、異なるパラメータを持つ同じルートに遷移する場合(例:ルート /page/:id
が与えられた場合に /page/1
から /page/2
に移動する場合)、両方のルートが同じコンポーネントに解決されるため、コンポーネントは最初から再作成されず、仮想 DOM 内でインプレースな差分が発生します。これには、oninit
/oncreate
ではなく onupdate
フックがトリガーされるという副作用があります。ただし、開発者がコンポーネントの再作成をルート変更イベントに同期させたいと思うのは比較的一般的です。
これを実現するために、ルートパラメータ化をkeyと組み合わせて、非常に便利なパターンを作成できます。
m.route(document.body, '/edit/1', {
'/edit/:key': Edit,
});
これは、ルートのルートコンポーネントに対して作成される vnode に、ルートパラメータオブジェクト key
が存在することを意味します。ルートパラメータは vnode の attrs
に格納されます。したがって、あるページから別のページにジャンプすると、key
が変更され、コンポーネントが最初から再作成されます(key によって仮想 DOM エンジンは、古いコンポーネントと新しいコンポーネントが異なるエンティティであると判断します)。
そのアイデアをさらに発展させて、リロード時に自身を再作成するコンポーネントを作成できます。
m.route.set(m.route.get(), {key: Date.now()})
または、履歴状態
機能を使用して、URL を汚染せずにリロード可能なコンポーネントを実現することもできます。
m.route.set(m.route.get(), null, {state: {key: Date.now()}})
key パラメータは、コンポーネントルートでのみ機能することに注意してください。ルートリゾルバーを使用している場合は、key: m.route.param("key")
を渡して、単一要素のキー付きフラグメントを使用する必要があります。
可変長ルート
可変長ルート、つまりスラッシュを含む URL パス名を含む引数を持つルートを持つことも可能です。
m.route(document.body, '/edit/pictures/image.jpg', {
'/edit/:file...': Edit,
});
404 の処理
同型/ユニバーサル JavaScript アプリケーションの場合、URL パラメータと可変長ルートの組み合わせは、カスタム 404 エラーページを表示するのに非常に役立ちます。
404 Not Found エラーが発生した場合、サーバーはカスタムページをクライアントに送り返します。Mithril.js がロードされると、ルートが不明なため、クライアントはデフォルトルートにリダイレクトされます。
m.route(document.body, '/', {
'/': homeComponent,
// [...]
'/:404...': errorPageComponent,
});
履歴状態
基盤となる history.pushState
API を最大限に活用して、ユーザーの遷移エクスペリエンスを向上させることができます。たとえば、アプリケーションは、ユーザーがナビゲートしてページを離れるときに、大きなフォームの状態を「記憶」することができます。これにより、ユーザーがブラウザの戻るボタンを押すと、空白のフォームではなく、フォームが入力された状態になります。
たとえば、次のようなフォームを作成できます。
var state = {
term: '',
search: function () {
// save the state for this route
// this is equivalent to `history.replaceState({term: state.term}, null, location.href)`
m.route.set(m.route.get(), null, {
replace: true,
state: { term: state.term },
});
// navigate away
location.href = 'https://google.com/?q=' + state.term;
},
};
var Form = {
oninit: function (vnode) {
state.term = vnode.attrs.term||''; // populated from the `history.state` property if the user presses the back button
},
view: function () {
return m('form', [
m("input[placeholder='Search']", {
oninput: function (e) {
state.term = e.target.value;
},
value: state.term,
}),
m('button', { onclick: state.search }, 'Search'),
]);
},
};
m.route(document.body, '/', {
'/': Form,
});
このようにして、ユーザーが検索して戻るボタンを押してアプリケーションに戻ると、入力フィールドには検索語が入力されたままになります。この手法は、大きなフォームや、永続化されていない状態をユーザーが作成するのが面倒な他のアプリケーションといったケースで、ユーザーエクスペリエンスを向上させることができます。
ルータープレフィックスの変更
ルータープレフィックスは、ルーターが使用する基盤となる戦略を規定する URL の一部です。
// パス名戦略に設定
m.route.prefix = '';
// クエリ文字列戦略に設定
m.route.prefix = '?';
// バンなしのハッシュに設定
m.route.prefix = '#';
// ルート以外の URL でパス名戦略に設定
// 例:アプリが `https://localhost/my-app` に存在し、別のものが
// `https://localhost` に存在する場合
m.route.prefix = '/my-app';
アドバンストコンポーネントの解決
コンポーネントをルートに対応付ける代わりに、RouteResolverオブジェクトを指定できます。RouteResolverオブジェクトは、onmatch()
メソッドおよび/またはrender()
メソッドを含みます。両方のメソッドは任意ですが、少なくとも1つは必須です。
m.route(document.body, '/', {
'/': {
onmatch: function (args, requestedPath, route) {
return Home;
},
render: function (vnode) {
return vnode; // m(Home) と同じ
},
},
});
RouteResolverは、様々な高度なルーティングのユースケースを実装するのに役立ちます。
レイアウトコンポーネントのラップ
ルーティングされたコンポーネントの多くを、再利用可能なラッパー(一般的に「レイアウト」と呼ばれる)でラップしたい場合があります。これを行うには、まず、さまざまなコンポーネントをラップする共通のマークアップを含むコンポーネントを作成する必要があります。
var Layout = {
view: function (vnode) {
return m('.layout', vnode.children);
},
};
上記の例では、レイアウトはコンポーネントに渡された子を含む<div class="layout">
のみで構成されていますが、実際のシナリオでは、必要に応じて複雑にすることができます。
レイアウトをラップする1つの方法は、ルートマップで匿名コンポーネントを定義することです。
// 例 1
m.route(document.body, '/', {
'/': {
view: function () {
return m(Layout, m(Home));
},
},
'/form': {
view: function () {
return m(Layout, m(Form));
},
},
});
ただし、トップレベルのコンポーネントは匿名コンポーネントであるため、/
ルートから/form
ルート(またはその逆)に遷移すると、匿名コンポーネントが破棄され、DOMが一から再作成されることに注意してください。Layoutコンポーネントにライフサイクルメソッドが定義されている場合、oninit
およびoncreate
フックはルートが変更されるたびに実行されます。アプリケーションによっては、これが望ましい場合と望ましくない場合があります。
Layoutコンポーネントを最初から再作成するのではなく、差分処理を行い、そのまま維持したい場合は、代わりにRouteResolverをルートオブジェクトとして使用する必要があります。
// 例 2
m.route(document.body, '/', {
'/': {
render: function () {
return m(Layout, m(Home));
},
},
'/form': {
render: function () {
return m(Layout, m(Form));
},
},
});
この場合、Layoutコンポーネントにoninit
およびoncreate
ライフサイクルメソッドがある場合、最初のルート変更でのみ実行されます(すべてのルートが同じレイアウトを使用していると仮定)。
2つの例の違いを明確にするために、例1は次のコードと同等です。
// 機能的には例1と同等
var Anon1 = {
view: function () {
return m(Layout, m(Home));
},
};
var Anon2 = {
view: function () {
return m(Layout, m(Form));
},
};
m.route(document.body, '/', {
'/': {
render: function () {
return m(Anon1);
},
},
'/form': {
render: function () {
return m(Anon2);
},
},
});
Anon1
とAnon2
は異なるコンポーネントであるため、それらのサブツリー(Layout
を含む)は最初から再作成されます。これは、コンポーネントがRouteResolverなしで直接使用される場合にも発生します。
例2では、Layout
は両方のルートのトップレベルコンポーネントであるため、Layout
コンポーネントのDOMは差分検出されます(つまり、変更がない場合はそのまま残ります)。Home
からForm
への変更のみが、DOMのそのサブセクションの再作成をトリガーします。
リダイレクト
RouteResolverのonmatch
フックを使用すると、ルートのトップレベルコンポーネントが初期化される前にロジックを実行できます。Mithrilのm.route.set()
またはネイティブHTMLのhistory
APIを使用することもできます。history
APIを使用してリダイレクトする場合、onmatch
フックは、一致するルートの解決を防ぐために、決して解決されないPromiseを返す必要があります。m.route.set()
は、一致するルートの解決を内部的にキャンセルするため、これを使用する必要はありません。
例:認証
以下の例は、ユーザーがログインしないと/secret
ページを表示できないようにするログインウォールを実装する方法を示しています。
var isLoggedIn = false;
var Login = {
view: function () {
return m('form', [
m(
'button[type=button]',
{
onclick: function () {
isLoggedIn = true;
m.route.set('/secret');
},
},
'Login'
),
]);
},
};
m.route(document.body, '/secret', {
'/secret': {
onmatch: function () {
if (!isLoggedIn) m.route.set('/login');
else return Home;
},
},
'/login': Login,
});
アプリケーションがロードされると、onmatch
が呼び出され、isLoggedIn
がfalseであるため、アプリケーションは/login
にリダイレクトされます。ユーザーがログインボタンを押すと、isLoggedIn
がtrueに設定され、アプリケーションは/secret
にリダイレクトされます。onmatch
フックが再度実行され、今回はisLoggedIn
がtrueであるため、アプリケーションはHome
コンポーネントをレンダリングします。
簡単にするために、上記の例では、ユーザーのログインステータスはグローバル変数に保持され、ユーザーがログインボタンをクリックすると、そのフラグが切り替えられるだけです。実際のアプリケーションでは、ユーザーは適切なログイン資格情報を提供する必要があり、ログインボタンをクリックすると、サーバーへのリクエストがトリガーされてユーザーが認証されます。
var Auth = {
username: '',
password: '',
setUsername: function (value) {
Auth.username = value;
},
setPassword: function (value) {
Auth.password = value;
},
login: function () {
m.request({
url: '/api/v1/auth',
params: { username: Auth.username, password: Auth.password },
}).then(function (data) {
localStorage.setItem('auth-token', data.token);
m.route.set('/secret');
});
},
};
var Login = {
view: function () {
return m('form', [
m('input[type=text]', {
oninput: function (e) {
Auth.setUsername(e.target.value);
},
value: Auth.username,
}),
m('input[type=password]', {
oninput: function (e) {
Auth.setPassword(e.target.value);
},
value: Auth.password,
}),
m('button[type=button]', { onclick: Auth.login }, 'Login'),
]);
},
};
m.route(document.body, '/secret', {
'/secret': {
onmatch: function () {
if (!localStorage.getItem('auth-token')) m.route.set('/login');
else return Home;
},
},
'/login': Login,
});
データのプリロード
通常、コンポーネントは初期化時にデータをロードできます。この方法でデータをロードすると、コンポーネントが2回描画されます。最初の描画パスはルーティング時に発生し、2番目の描画パスはリクエストが完了した後に発生します。loadUsers()
がPromiseを返すことに注意してください。ただし、oninit
によって返されるPromiseは現在無視されます。2番目の描画パスは、m.request
のbackground
オプションによるものです。
var state = {
users: [],
loadUsers: function () {
return m.request('/api/v1/users').then(function (users) {
state.users = users;
});
},
};
m.route(document.body, '/user/list', {
'/user/list': {
oninit: state.loadUsers,
view: function () {
return state.users.length > 0
? state.users.map(function (user) {
return m('div', user.id);
})
: 'loading';
},
},
});
上記の例では、最初レンダリングでは、リクエストが完了する前にstate.users
が空の配列であるため、UIに"loading"
が表示されます。次に、データが利用可能になると、UIが再描画され、ユーザーIDのリストが表示されます。
RouteResolverは、コンポーネントのレンダリング前にデータをプリロードすることで、UIのちらつきを防ぎ、ローディングインジケータを不要にするメカニズムとして利用できます。
var state = {
users: [],
loadUsers: function () {
return m.request('/api/v1/users').then(function (users) {
state.users = users;
});
},
};
m.route(document.body, '/user/list', {
'/user/list': {
onmatch: state.loadUsers,
render: function () {
return state.users.map(function (user) {
return m('div', user.id);
});
},
},
});
上記では、render
はリクエストが完了した後にのみ実行されるため、三項演算子は不要になります。
コードスプリッティング
大規模なアプリケーションでは、各ルートのコードを事前にダウンロードするのではなく、必要に応じてオンデマンドでダウンロードしたい場合があります。この方法でコードベースを分割することは、コードスプリッティングまたは遅延ロードと呼ばれます。Mithril.jsでは、これはonmatch
フックからPromiseを返すことによって実現できます。
最も基本的な形式では、次のようになります。
// Home.js
module.export = {
view: function () {
return [m(Menu), m('h1', 'Home')];
},
};
// index.js
function load(file) {
return m.request({
method: 'GET',
url: file,
extract: function (xhr) {
return new Function(
'var module = {};' + xhr.responseText + ';return module.exports;'
);
},
});
}
m.route(document.body, '/', {
'/': {
onmatch: function () {
return load('Home.js');
},
},
});
ただし、現実的には、それが実際の運用環境で機能するためには、Home.js
モジュールのすべての依存関係を、最終的にサーバーによって提供されるファイルにバンドルする必要があります。
幸いなことに、遅延ロード用のモジュールをバンドルするタスクを容易にするツールが多数あります。以下は、多くのバンドラーでサポートされているネイティブ動的import(...)
を使用した例です。
m.route(document.body, '/', {
'/': {
onmatch: function () {
return import('./Home.js');
},
},
});
タイプ付きルート
特定の高度なルーティングのケースでは、パスだけでなく、数値IDのように値をさらに制約したい場合があります。ルートからm.route.SKIP
を返すことで、それを簡単に行うことができます。
m.route(document.body, '/', {
'/view/:id': {
onmatch: function (args) {
if (!/^\d+$/.test(args.id)) return m.route.SKIP;
return ItemView;
},
},
'/view/:name': UserView,
});
隠しルート
まれな状況では、一部のユーザーに対して特定のルートを非表示にしたい場合があります。例えば、特定のユーザーの表示が禁止されている場合に、権限エラーを表示する代わりに、存在しないものとして扱い、404ビューにリダイレクトする、といったことが考えられます。この場合、m.route.SKIP
を使用して、ルートが存在しないかのように振る舞わせることができます。
m.route(document.body, '/', {
'/user/:id': {
onmatch: function (args) {
return Model.checkViewable(args.id).then(function (viewable) {
return viewable ? UserView : m.route.SKIP;
});
},
},
'/:404...': PageNotFound,
});
ルートのキャンセル/ブロック
RouteResolverのonmatch
は、決して解決されないPromiseを返すことで、ルートの解決を阻止できます。これは、冗長なルート解決の試みを検出し、キャンセルするために使用できます。
m.route(document.body, '/', {
'/': {
onmatch: function (args, requestedPath) {
if (m.route.get() === requestedPath) return new Promise(function () {});
},
},
});
サードパーティの統合
特定の状況では、Reactのような別のフレームワークと相互運用する必要がある場合があります。その方法を次に示します。
- 通常どおり
m.route
を使用してすべてのルートを定義しますが、_一度だけ_使用してください。複数のルートポイントはサポートされていません。 - ルーティングのサブスクリプションを削除したい場合は、
m.mount(root, null)
を使用し、m.route(root, ...)
で使用したのと同じルートを指定します。m.route
は内部的にm.mount
を使用してすべてを接続するため、特別な処理ではありません。
Reactを使用した例を次に示します。
class Child extends React.Component {
constructor(props) {
super(props);
this.root = React.createRef();
}
componentDidMount() {
m.route(this.root, '/', {
// ...
});
}
componentDidUnmount() {
m.mount(this.root, null);
}
render() {
return <div ref={this.root} />;
}
}
Vueを使用したほぼ同等の例を次に示します。
<div ref="root"></div>
Vue.component('my-child', {
template: `<div ref="root"></div>`,
mounted: function () {
m.route(this.$refs.root, '/', {
// ...
});
},
destroyed: function () {
m.mount(this.$refs.root, null);
},
});