route(root, defaultRoute, routes)
描述
在應用程式中的「頁面」之間導覽。
var Home = {
view: function () {
return 'Welcome';
},
};
m.route(document.body, '/home', {
'/home': Home, // 定義 `https://localhost/#!/home`
});
每個應用程式只能有一個 m.route
呼叫。
簽名
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
此元件用於建立動態路由連結。它的基本功能是產生 a
連結,其本機 href
會轉換以考量 路由前綴。
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
方法。這兩種方法都是可選的,但必須至少存在一種。
如果可以檢測到一個物件是元件(通過存在 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
鉤點。每個路由器路徑變更都會呼叫一次,但在同一路徑上的後續重新繪製中不會呼叫。它可用於在元件初始化之前執行邏輯(例如,身份驗證邏輯、資料預載、重新導向分析追蹤等)。
此方法還允許您非同步定義將渲染的元件,使其適用於程式碼分割和非同步模組載入。要非同步渲染元件,請回傳一個解析為元件的 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
參數結合使用,它會變成一個 單元素鍵控片段,因為它最終會渲染為類似 [m(Component, {key: m.route.param("key"), ...})]
的內容。
它是如何運作的
路由是一個允許建立單頁應用程式 (SPA) 的系統,也就是可以從一個「頁面」跳到另一個頁面而不會導致瀏覽器完全重新整理的應用程式。
它實現了無縫導航,同時保留了單獨為每個頁面添加書籤的能力,以及通過瀏覽器的歷史紀錄機制導航應用程式的能力。
在沒有頁面重新整理的情況下進行路由部分是由 history.pushState
API 實現的。使用此 API,可以在頁面載入後以程式設計方式變更瀏覽器顯示的 URL,但應用程式開發人員有責任確保從冷狀態(例如,新標籤)導航到任何給定的 URL 都會渲染適當的標記。
路由策略
路由策略決定了程式庫實際上如何實現路由。有三種通用策略可用於實現 SPA 路由系統,每種策略都有不同的注意事項:
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,請使用此策略。
使用雜湊策略的單頁應用程式通常使用在雜湊後帶有驚嘆號的約定,以指示它們使用雜湊作為路由機制,而不是用於連結到錨點的目的。#!
字串稱為 hashbang。
預設策略使用 hashbang。
典型用法
通常,您需要建立一些 元件 以將路由映射到:
var Home = {
view: function () {
return [m(Menu), m('h1', 'Home')];
},
};
var Page1 = {
view: function () {
return [m(Menu), m('h1', 'Page 1')];
},
};
在上面的範例中,有兩個元件:Home
和 Page1
。每個元件都包含一個選單和一些文字。選單本身被定義為一個元件,以避免重複:
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
,當使用者導覽到每個 URL 時,它們會渲染它們各自的元件。
導航到不同的路由
在上面的範例中,Menu
元件有兩個 m.route.Link
。這會建立一個元素,預設為 <a>
,並設定為當使用者點擊它時,自行導航到另一個路由。它不會遠端導航,只是在本機導航。
您也可以通過 m.route.set(route)
以程式設計方式導航。例如,m.route.set("/page1")
。
在路由之間導覽時,路由器前綴會自動為您處理。換句話說,在連結 Mithril.js 路由時,請省略 hashbang #!
(或您設定 m.route.prefix
的任何前綴),包括在 m.route.set
和 m.route.Link
中。
請注意,在元件之間導航時,會替換整個子樹。如果您只想修補子樹,請使用 具有 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
。
鍵參數
當使用者從參數化路由導航到同一路由但具有不同參數時(例如,在給定路由 /page/:id
的情況下,從 /page/1
轉到 /page/2
),元件不會從頭開始重新建立,因為兩個路由都解析為同一元件,因此會導致虛擬 DOM 就地差異。這具有觸發 onupdate
鉤點的副作用,而非 oninit
/oncreate
。但是,開發人員相對常見的是希望將元件的重新建立與路由變更事件同步。
為了實現這一點,可以將路由參數化與 鍵 結合使用,以獲得非常方便的模式:
m.route(document.body, '/edit/1', {
'/edit/:key': Edit,
});
這意味著為路由的根元件建立的 vnode 具有路由參數物件 key
。路由參數成為 vnode 中的 attrs
。因此,當從一個頁面跳到另一個頁面時,key
會變更,並導致元件從頭開始重新建立(因為鍵告訴虛擬 DOM 引擎舊元件和新元件是不同的實體)。
您可以進一步延伸這個想法,以建立在重新載入時重新建立自身的元件:
m.route.set(m.route.get(), {key: Date.now()})
甚至可以使用 history state
功能來實現可重新載入的元件,而不會污染 URL:
m.route.set(m.route.get(), null, {state: {key: Date.now()}})
請注意,鍵參數僅適用於元件路由。如果您使用路由解析器,則需要使用 單元素鍵控片段,傳遞 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 () {
// 儲存此路由的狀態
// 這相當於 `history.replaceState({term: state.term}, null, location.href)`
m.route.set(m.route.get(), null, {
replace: true,
state: { term: state.term },
});
// 導航離開
location.href = 'https://google.com/?q=' + state.term;
},
};
var Form = {
oninit: function (vnode) {
state.term = vnode.attrs.term || ''; // 如果使用者按下後退按鈕,則從 `history.state` 屬性填充
},
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 = '?';
// 設定為沒有 bang 的雜湊
m.route.prefix = '#';
// 在非根 URL 上設定為路徑名稱策略
// 例如,如果應用程式位於 `https://localhost/my-app` 下,而其他內容位於 `https://localhost` 之下
m.route.prefix = '/my-app';
進階元件解析度
除了將元件對應到路由之外,您還可以指定一個 RouteResolver
物件。RouteResolver
物件包含一個 onmatch()
和/或一個 render()
方法。這兩個方法都是可選的,但至少必須存在一個。
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
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
生命週期方法,它們只會在第一次路由變更時觸發(假設所有路由都使用相同的佈局)。
為了說明這兩個範例之間的差異,範例 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,
});
預載資料
通常,元件可以在初始化時載入資料。以這種方式載入資料會渲染元件兩次。第一次渲染發生在路由時,第二次在請求完成後觸發。請注意,loadUsers()
返回一個 Promise,但 oninit
返回的任何 Promise 目前都會被忽略。第二次渲染來自 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';
},
},
});
在上面的範例中,在第一次渲染時,UI 顯示 "loading"
,因為在請求完成之前 state.users
是一個空陣列。然後,一旦資料已載入,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);
},
});