本系列一共七章,Github 地址請查閱這裡,原文地址請查閱這裡。
前端路由
這是編寫前端框架系列的最後一章。本章,我將會討論前端路由和後端路由的不同以及為什麼他們應被區別對待。
網頁上的路由
網頁無非是後端渲染,前端渲染或者兩者混合渲染。不管怎樣,一個半複雜的網頁不得不處理路由。
對於後端渲染,路由是由後端處理的。當 URL 路徑改變或者請求引數改變的時候會輸出一個新的頁面,這對於傳統網頁是完美的解決方案。然而網頁程式經常需要保持當前使用者的狀態,這在海量的服務端渲染的頁面之間是很難維護的。
客戶端框架通過預讀取程式和在儲存的頁面間切換並保持狀態來解決這些問題。前端路由的實現與伺服器端的路由非常相似。唯一的區別是它直接從前端而不是後端獲取資源。本篇文章中,我將會解釋為什麼這兩者需要稍微處理得有些不同。
由後端啟發的路由
許多前端路由庫都是由後端啟發的。
他們只是在 url 改變的時候執行適當的路由處理程式,從而啟動和渲染所需的元件。前端和服務端的結構是類似的,唯一的區別即是處理函式所做的事情。
為了演示其相似性,你可以在如下的程式碼中發現服務端 Express 框架,前端程式碼 page.js 路由和 React 相同的路由程式碼片段。
// Express
app.get(`/login`, sendLoginPage)
app.get(`/app/:user/:account`, sendApp)
複製程式碼
// Page.js
page(`/login`, renderLoginPage)
page(`/app/:user/:account`, renderApp)
複製程式碼
<!-- React -->
<Router>
<Route path="/login" component={Login}/>
<Route path="/app/:user/:account" component={App}/>
</Router>
複製程式碼
React 隱藏了一些 JSX 背後的邏輯,但是它們做的都是同樣的事情,在引入動態引數之前,它們都工作得很完美。
在上面的例子中,一個使用者可能有多個賬號並且當前賬戶可以隨意更改。如果在 App
頁面改變賬戶名,對應的處理程式為新的賬戶名重啟或者重發相同的 App
元件 – 然而其實只需要更改現存元件裡面的一些資料即可。
對於虛擬 DOM 解決方案這只是小菜一碟,因為它們會查詢 DOM 的差異,然後只更新需要的部分 – 但是對於傳統框架,這意味著更多不必要的工作。
處理動態引數
當引數改變的時候重新渲染整個頁面是我想要避免的。為了解決這個問題,我先從動態引數中分離出路由。
在 NX 中,路由會決定顯示哪個元件或檢視,然後進入到 URL 路徑名。動態引數控制當前頁面顯示的資料,他們總是在查詢引數裡面。
這意味著 /app/:user/:account
將會轉換為 /app?user=userId&account=accountId
。他略顯冗長,但更加清晰,並且它允許我把客戶端路由分為頁面路由和引數路由。前者在 app 殼中導航,而後者在資料殼中進行導航。
app 殼
你或許會熟悉 app 殼模型,它在谷歌的 PWA 程式中得到推廣。
app 殼是一個用來驅動使用者介面的最小化的 HTML,CSS 和 JavaScript。
在 NX 中,路徑路由負責在 app 殼中導航。一個簡單的路由結構如下所示。
<router-comp>
<h2 route="login"/>Login page</h2>
<h2 route="app"/>The app</h2>
</router-comp>
複製程式碼
這和之前的例子類似 – 特別是和 React 例子 – 但是這裡有一個主要的區別。它沒有處理 user
和 account
引數。相反,它只是在空的app shell中導航。
這讓它成為一個非常簡單的樹遍歷問題。路由樹被遍歷 – 基於 URL 路由 – 然後它以它的方式顯示元件。
以上圖表解釋了當前檢視是如何切換為 /settings/profile
地址的。你可以找到如下相應的程式碼。
nx.components.router()
.register(`router-comp`)
複製程式碼
<a iref="home">Home</a>
<a iref="settings">Settings</a>
<router-comp>
<h2 route="home" default-route>Home page</h2>
<div route="settings">
<h2>Settings page</h2>
<a iref="./profile">Profile</a>
<a iref="./privacy">Privacy</a>
<router-comp>
<h3 route="profile" default-route>Profile settings</h3>
<h3 route="privacy">Privacy settings</h3>
</router-comp>
</div>
</router-comp>
複製程式碼
這個示例展示了擁有預設和相對路由的巢狀路由結構。如你所見,只用 HTML 配置相當的簡單,並且它和大多數的檔案系統執行原理類似。你可以在它裡面使用絕對路徑 home
和相對路徑 ./privacy
連結來導航。路由片段執行效果如下圖所示。
這個簡單的結構可以被濫用來建立強大的模式。一個例子是並行路由,指的是同一時間遍歷多個路由樹。側邊選單欄和 NX docs page 的內容都是這樣工作的。它有兩個並行的內嵌路由,同時改變側邊導航和頁面的內容。
資料殼
和 app 殼不同,`data shell` 並不是一個炒作的術語。事實上,它只供我使用,它指的是用動態引數池來驅動資料流。它不是更改當前頁面,它只更新頁面的資料。改變當前頁面通常會改變引數池,但改變引數池的引數不會導致頁面重新整理。
通常情況下,資料殼是由一組原始值-還有當前頁面所組成,它表示當前程式的狀態。因此,它可以用來儲存載入和分享狀態。為了達到這個目的,它必須在 URL,localStorage 或者瀏覽器歷史中體現-這使它實質上是全域性的。
NX 眾多元件之中的控制元件可以通過宣告性配置連線到引數池,它會決定引數如何和元件的狀態,URL,瀏覽器歷史和網頁儲存進行互動。
nx.components.control({
template: require(`./view.html`),
params: {
name: { history: true, url: true, default: `World` }
}
}).register(`greeting-comp`)
複製程式碼
<p>Name: <input type="text" name="name" bind/></p>
<p>Hello @{name}</p>
複製程式碼
以上示例程式碼建立了一個元件,使得 name
屬性與 URL 和瀏覽器歷史保持同步。你可以在如下看到效果。
多虧了基於 ES6 代理的透明反射,同步是無縫的。你可以書寫 vanilla JavaScript,所有的東西都會在後臺按需雙向繫結。下圖給出了一個這方面的深入概述。
簡單的宣告式的語法鼓勵開發者在程式設計之前花幾分鐘時間設計頁面的網頁整合。並不是所有的引數都應該進入 URL 或者當頁面改變的時候新增一個新的歷史記錄項。有大量的不同情況,每種情況都得進行適當配置。
- 一個簡單的文字過濾器應該是一個
url
引數,因為它應該可以與其他使用者共享。 - 一個賬戶 id 應該是一個
url
和history
引數,因為目前的賬戶應該是可分享,並且改變它是足以用來新增一個新的歷史記錄項。 - 一個視覺選項應該是一個可持久的引數(儲存在本地儲存中),因為它應該為每個使用者持久化,並且不應該被共享。
這只是一些可能的設定。只需要最少的努力,你就可以真正地讓這些引數完美地適用於你的使用場景。
總結
路徑路由和引數路由是互相獨立的,他們被設計成可以很好地一起協作。路徑路由導航到 app 殼中的預期頁面,引數路由接收並且並管理狀態和資料外殼。
不同頁面中的引數池也許會不一樣,所以在 JavaScript 和 HTML 中都有一個顯式的 API 來改變當前頁面和引數。
<a iref="newPage" $iref-params="{ newParam: `value` }"></a>
comp.$route({
to: `newPage`,
params: { newParam: `value` }
})
複製程式碼
在這個基礎上,NX 自動在啟用的連結上面新增 active
樣式類名,這樣你可以使用配置引數裡面的 options
配置所有的常見路由功能比如引數繼承和路由事件。
開啟 routing docs 來檢視更多的功能。
前端路由示例
如下示例演示了引數路由與反應性資料流的結合。這是一個完全可以工作的 NX app。只要把如下程式碼拷貝進一個空的 HTML 檔案中, 然後在現代瀏覽器中開啟它來試用。
<script src="https://www.nx-framework.com/downloads/nx-beta.2.0.0.js"></script>
<script>
nx.components.app({
params: {
title: { history: true, url: true, default: `Gladiator` }
}
}).use(setup).register(`movie-plotter`)
function setup (comp, state) {
comp.$observe(() => {
fetch(`http://www.omdbapi.com/?r=json&t=` + state.title)
.then(response => response.json())
.then(data => state.plot = data.Plot || `No plot found`)
})
}
</script>
<movie-plotter>
<h2>Movie plotter</h2>
<p>Title: <input type="text" name="title" bind /></p>
<p>Plot: @{plot}</p>
</movie-plotter>
複製程式碼
狀態中的 title
屬性會自動與 URL 和 瀏覽器歷史保持同步。傳入函式中的 comp.$observe
會被監聽,當標題改變的時候,它會自動抓取對應的電影情節。這會建立一個強大的完美地和瀏覽器結合的反應式資料流。
這個 app 沒有展示路徑路由。想看更多的完整示例請檢視 intro app,NX Hacker News clone 或者 path routing 和 parameter routing 文件頁面。都有可編輯示例。
打個廣告 ^.^
今日頭條招人啦!傳送簡歷到 likun.liyuk@bytedance.com,即可走快速內推通道,長期有效!國際化PGC部門的JD如下:c.xiumi.us/board/v5/2H…,也可內推其他部門!