前端框架簡介
我們從整體概述來探討框架、提供 JavaScript 與框架的簡要歷史、框架存在的理由、他們提供什麼東西、如何決定選擇哪個框架、以及前端框架的的替代方案。
先決條件: | 熟悉 HTML、CSS、JavaScript 這些核心技術。 |
---|---|
目標: | 理解 JavaScript 前端框架存在的理由、他們解決的問題、可用的替代方案、還有決定選擇的方法。 |
一段簡短的歷史
在 JavaScript 誕生的 1996 年,它的作用僅僅是為當時由靜態文檔組成的網頁,提供些許的互動和興奮感。而當網路漸漸從閱讀的媒介,成為做事的場所,JavaScript 也慢慢地紅了起來。JavaScript 開發者們撰寫了許多工具,來解決自己遭遇的問題,並打包成能重複使用的工具包,稱為函式庫(library),以便和他人共享自己的解決方案。這個共享函式庫的生態,也推進了網路的增長。
目前 JavaScript 已經是網路的必需品了,大約 95% 的網站都又在使用 JavaScript,網路也成了當今生活的必須。使用者可以透過文字與影像,來寫論文、聽音樂、看電影、與人遠距離交流。曾經只能透過裝在電腦內的原生軟體所完成的事情,現在也能網路上做到。這種現代化、複雜度高、還有各種互動的網站,被稱為網路應用程式(web applications)。
當代 JavaScript 框架的問世,讓構建高度動態的互動式應用,變得簡單許多。框架是個針對軟體構建,提供完整解決方案的函式庫。這些選項能讓應用程式,開始能預測和同質化。可預測性讓軟體能擴展到巨大的規模時依舊能維護;可預測性和可維護性則對軟體的健康和長壽至關重要。
JavaScript 框架能構建常用的網站裡,許多令人印象深刻的軟體。目前你正在看的 MDN Web Docs 網站,也是用 React/ReactDOM 作為前端支援的。
那有什麼框架?
有很多框架,不過主要有以下「四大框架」。
Ember
Ember 在 2011 年 12 月發行。這個框架始於 SproutCore 的內部專案。這是個比較老的框架:與 React 或 Vue 之類的替代方案相比,其用戶數比較少,不過在穩定性、社區支持、和一些巧妙的編碼原則方面,仍然享譽無數。
Angular
Angular 是個由 Google 內部的 Angular Team 與其他社群所開發的開源專案。這個專案是同一群人由 AngularJS 所重寫的專案。該專案於 2016 年 9 月 14 日發行。
Angular 是基於組件、並使用指令式 HTML 樣板的框架。在構建時,框架的編譯器會將模板,轉換為優化的 JavaScript 程式。Angular 使用了 JavaScript 超集(superset)的 TypeScript。我們將在下一章中詳細介紹它。
Vue
React
Facebook 於 2013 年發表了 React。在發表當時 Facebook 內部早已使用 React 解決許多內部問題。技術上來說 React 並不是框架,而是一個用來渲染 UI 組件的函式庫。React 通常會配合其他函式庫來建立應用程式:例如 React 搭配 React Native 建立手機程式、React 與 ReactDOM 建立網路程式...等等。
由於 React 與 ReactDOM 通常會搭在一起用,React 在通俗上會被理解為 JavaScript 框架。在閱讀本模塊時,我們將以這種通俗理解為基礎。
React 使用一種很像是 HTML 的 JavaScript 語法:JSX。
為什麼有框架?
我們已經討論了啟發框架建立的環境,但那不是開發人員為什麼要製造它們的理由。要探索原因,首先需要首先檢查開發軟體所碰上的挑戰。
來看看一個常見的例子吧:一個能建立待辦事項程式,我們將會用待辦事項程式為例子,來介紹不同的框架。這個應用程序要能讓使用者執行諸如渲染事項列表,添加新任務和刪除任務之類的操作;還要能可靠地跟踪並更新程式所依賴的資料在軟體開發中,此這些資料稱為狀態(state)。
每個目標分開來看,理論上都很簡單:我們能遍歷需要渲染的資料、建立新的工作物件、還能用標識符來查詢、編輯或刪除工作。然而,當我們要求程式,讓用戶在瀏覽器完成這一切的話,麻煩就來了。問題在於:更動狀態時,也同時需要更動 UI 的顯示。
讓我們藉由待辦事項程式的一個功能來看看這個問題有多難搞:把工作清單渲染出來。
DOM 的冗長變化
建立 HTML 元素並在瀏覽器上渲染,會需要驚人數量的程式碼。假設我們的狀態,是一個由多個物件組成的陣列:
const state = [
{
id: "todo-0",
name: "Learn some frameworks!",
},
];
我們如何對用戶顯示工作?我們想將每個工作,都表示為一個列表項目:結構為無序列表元素 <ul>
內,含有一定數量的 <li>
元素。怎麼做呢?看起來就像這樣:
function buildTodoItemEl(id, name) {
const item = document.createElement("li");
const span = document.createElement("span");
const textContent = document.createTextNode(name);
span.appendChild(textContent);
item.id = id;
item.appendChild(span);
item.appendChild(buildDeleteButtonEl(id));
return item;
}
我們在這裡用上了 document.createElement()
方法建立了 <li>
、還有一些程式碼來建立需要的屬性與子元素。
程式的第十行引用了另一個構建函式:buildDeleteButtonEl()
。它與用於構建列表元素的模式很像:
function buildDeleteButtonEl(id) {
const button = document.createElement("button");
const textContent = document.createTextNode("Delete");
button.setAttribute("type", "button");
button.appendChild(textContent);
return button;
}
這個按鈕還派不上用場,但稍後我們會用它來實做刪除功能。這渲染程式,會讓頁面看起來像這樣:
function renderTodoList() {
const frag = document.createDocumentFragment();
state.tasks.forEach((task) => {
const item = buildTodoItemEl(task.id, task.name);
frag.appendChild(item);
});
while (todoListEl.firstChild) {
todoListEl.removeChild(todoListEl.firstChild);
}
todoListEl.appendChild(frag);
}
光是為了弄 UI 我們就寫了大約三十行左右的程式:這樣就只是為了能讓清單在 DOM 渲染而已喔。更別提之後還需要添加方便樣式化的 class。
像範例這樣直接操作 DOM 的話,會需要理解很多 DOM 的東西:像是 DOM 的原理、如何建立元素、更改屬性、巢狀排列……同時還要把他們都呈現出來。這些程式甚至還沒有處理與用戶的互動或工作。在增加功能的時候,我們需要在正確的時間、用正確的方法,來更新我們的 UI。
JavaScript 框架旨在使這類工作更加輕鬆:他們會提供更好的開發體驗。框架本身並沒有給 JavaScript 提供新功能;而是用更容易的方案,建立當代的網站。
如果想查看本節中的程式碼範例,請參閱 CodePen 的程式:這網站同樣允許用戶添加和刪除新任務。
閱讀本節中使用的 JavaScript 資訊:
建立 UI 的另一種方法
JavaScript 框架都會提供一種能更加宣告性撰寫介面的方法。也就是說,框架能讓你描述 UI 看起來要怎麼樣、然後在 DOM 的背後完成這一切。
原生 JavaScript 試圖重複建立新 DOM 元素的方法,很難一眼理解。相反地,來看看 Vue 的程式碼會怎麼完成吧:
<ul>
<li v-for="task in tasks" v-bind:key="task.id">
<span>{{task.name}}</span>
<button type="button">Delete</button>
</li>
</ul>
就這樣啦。原本大約三十多行的程式,現在只要六行。如果不太熟悉大括號和 v-
屬性的話,沒關係;那些語法會在 Vue 模塊學到。這裡只是要講說與原生 JavaScript 對比,框架的程式碼看起來更像是實際呈現的 UI。
也因為有 Vue,我們再也不用自己寫針對 UI 呈現的函式;整個框架會幫我們用最優化、最效率的方法完成這件事。我們只要告訴 Vue 整個排版要怎麼排就好。熟悉 Vue 的開發者,也可以盡快參與我們的專案、搞清楚整個專案到底怎麼做的。不過並不是只有 Vue 這樣:使用框架本身,就可以提高團隊與個人的工作效率。
要在原生 JavaScript 做類似的事是可以的。樣板文字就能在編寫 HTML 字串的同時,也表示最終元素的外觀。就算是待辦事項列表應用,這樣簡單的事情,這可能是一個有用的想法,但是對於管理成千上萬條數據記錄、並且要在用戶界面中呈現盡可能多唯一元素的大型應用程序而言,這樣子是很難維護的。
框架給我們的其他東西
讓我們看看框架賦予的其他優勢。正如之前提,你可以透過原生 JavaScript 實現框架,但是使用框架可以消除自行解決這些問題所需要的認知負擔。
工具
本模塊提到的幾個框架,背後都有著龐大而活躍的社群;這些社群形成了各種生態圈、並提供能增進開發體驗的工具:像是確保功能正常的測試、或著維持程式一致性的 linting。
備註:如果對這方面的概念有興趣,請看看 Client-side tooling overview。
切分
大多數框架鼓勵開發者把介面的各部份,封裝成各種組件(components):也就是一個個可維護、可互通、還可重用的程式碼塊(chunks of code)。與該組件相關連的程式,可獨立為一個、或數個獨立的程式。在原生 JavaScript 裡面要這麼做的話,開發者就必須靠自己的慣例,才能在高效、可擴展的情況下,實現這個目標。但大多數的 JavaScript 開發者,最後會讓 UI 相關的所有程式,都分散在整個文件中。
路由
web 最重要的功能之一,就是頁面之間的導航:畢竟它就是相互連接文件的網路。在你點選網站上的連結時,瀏覽器會與伺服器溝通、並獲取新內容以便顯示給你看。也因為這樣,地址欄中的 URL 就會更改。你可以保存這個新的 URL 並稍後回來、或與其他人分享該 URL,以便他們輕鬆地找到同一個頁面。你的瀏覽器會記住這個導航歷史記錄,也能在頁面之間來回導航。這就叫伺服器端路由(server-side routing)。
現代的網路應用程式通常不獲取和渲染新的 HTML 文件:它們通常載入單個 HTML Shell,並不斷更新其中的 DOM 同時,不導航用戶到新地址(這被稱為單頁應用、single page apps、SPA)。每個新的虛擬網頁通常稱為 視圖(view),一般來說也不執行路由。
在 SPA 複雜到一定的程度、也有渲染出足夠獨特的視圖時,給應用程式導入路由功能,就變得很重要:人們習慣在應用程式中,透過連結導航到特定頁面,在導航歷史記錄中前進和後退等,而當這些標準的 Web 功能被破壞時,他們的體驗也會受到影響。如果導航功能以用戶端程式提供,這就叫做用戶端路由(client-side routing)。
你可以透過原生 JavaScript 實做路由功能。但比較活躍的框架都有相對應的函式庫,讓路由功能在開發過程中更加直觀。
使用框架時要考慮的事情
想成為一位高效的網路開發,意味著你需要選擇最合適的工具:JavaScript 框架能讓前端開發變簡單,但它並不是萬能仙丹。我們回在這個章節探討選擇框架需要考慮的事情。請注意,你可能完全不需要框架。不要為了用框架而用框架。
熟悉工具
如同原生 JavaScript,框架也需要理解它們各自的特性。在選好框架前,確保有足夠的時間熟悉框架,以便讓它成為開發的墊腳石,而不是絆腳石。同時,也要確保你的同事們能接受它。
過度工程化
一個 web 專案如果是個只有數個個人頁面、還幾乎沒有交互功能的話,你完全不需要 JavaScript 框架、甚至 JavaScript 本身也不需要。也就是說,框架並不是整體式的,其中一些可能更適合於小型專案。一篇在 Smashing Magazine 的文章中,作者 Sarah Drasner 寫了一篇怎麼用 Vue 取代 jQuery 的文章、讓網頁的一小部分具有交互性。
更大的程式庫和抽象化
框架能寫出宣告式程式、有時候還會少寫程式。這一切都是透過框架在背後處理 DOM 互動所達成的。這種抽象化給開發者提供了相當棒的體驗,但這並不是免費的。為了把編寫的程式變成能與 DOM 互動的玩意,框架必須執行自己的程式;而這反過來又會讓專案變大,執行演算的開銷也更高昂。
不可避免地,這會產生一些額外的程式;而儘管一個支持 tree-shaking(刪除在構建過程中未實際用到的程式)的框架,會讓應用程式保持小巧,但在考慮性能時,這將是必須牢記在心的因素之一,尤其是在受網路/儲存空間受限的設備上(像是手機)。
框架的抽象化不僅影響你的 JavaScript、它也會影響你對與網絡本質的關係:無論如何構建 Web,最後與用戶交流的都是 HTML。用 JavaScript 編寫整個程式,會讓你看不到 HTML 本身、以及各標籤的用途,最後會生出不語義、且有障礙的 HTML 文件。實際上,你甚至能寫出一個完全依賴 JavaScript,沒有它就完全動不了的脆弱應用程式。
框架不是問題的根源。如果優先事項設錯了,任何應用程式都會變得脆弱、腫大、且障礙多舛。不過,框架確實擴大了我們作為開發人員的優先事項。如果想做出一個很複雜的 Web 程式,這當然能作到;但如果優先事項無法確保性能與無障礙的話,框架將放大這方面的問題。現在的 Web 不再是一個健壯的,內容優先的文件網絡,而是常將 JavaScript 放在首位,用戶體驗則放在最後。
框架網站的無障礙議題
讓我們以上一節的內容為基礎,並進一步討論無障礙問題。消除用戶界面的障礙總是需要點思考與努力,而框架會使該過程複雜化。你通常要用上進階的框架 API 來訪問本機瀏覽器功能,例如 ARIA live region 或 focus 管理。
在某些情況下,框架應用程式會發生在傳統網站不存在的障礙。最明顯的例子,就是前述的客戶端路由。
使用傳統(伺服器端)路由瀏覽 Web 會出現可預測的結果。瀏覽器知道將焦點設置在頁面頂部、輔助技術將宣布頁面標題。在導航到新頁面時,這些事情都一定會發生。
使用客戶端路由時,瀏覽器不會加載新頁面,因此它不知道要自動調整焦點、或宣告新的頁面標題。框架作者會花費大量時間和精力,來編寫可重現這些功能的 JavaScript,但沒有一個框架能做到如此完美。
結論是,所有的 Web 專案一開始,就要應該考慮無障礙問題。如果專案使用抽象的框架、又不考慮無障礙問題的話,未來衍生的無障礙問題會更嚴重。
如何選擇框架
不同模塊的框架,會採用不同的方法。來開發 Web 應用程式。框架都會定期變化、也都有其優缺點。選擇哪個框架的過程,是與團隊及專案息息相關的。你需要透過研究,來找出合適的需求。換句話說,我們已經找出了一些能有效地研究出選擇的問題:
- 框架支援哪些瀏覽器?
- 框架使用哪個特定領域語言(domain-specific language)?
- 框架有夠大的社群與夠好的文件(或其他東西)支援嗎?
接下來我們退提供一個表格,來展示各大框架的瀏覽器支援、還有能用的特定領域語言。
一般來說,特定領域語言(domain-specific language, DSL)是一種與的特定領域軟體開發相關的程式語言。以框架的脈絡來說,DSL 是能讓開發更簡單的 JavaScript 或 HTML 變體。最重要的是,沒有哪個框架要求開發者使用某種特定領域語言,但框架們在挑選 DSL 方面,早已心有所屬了。選擇不採用該框架的首選 DSL,可能就會失去本可增添開發人員體驗的功能。
在為任何新專案做出選擇時,你需要認真考慮框架的支持矩陣(support matrix)和 DSL。瀏覽器要是不支援,會成為用戶的障礙;而 DSL 要是不支援,則會你和你開發團隊的障礙。
框架 | 瀏覽器支援 | 首選的 DSL | 支援的 DSL |
---|---|---|---|
Angular | IE9+ | TypeScript | 基於 HTML; TypeScript |
React | 當代瀏覽器(IE9+ 含有 Polyfill) | JSX | JSX; TypeScript |
Vue | IE9+ | 基於 HTML | 基於 HTML, JSX, Pug |
Ember | 當代瀏覽器(IE9+ 直到 2.18 為止) | Handlebars | Handlebars, TypeScript |
備註:「基於 HTML」的 DSL 並沒有官方名字。雖然它們不完全是 DSL,但也不是標準的 HTML,所在我們在此附帶一題。
表格的引用來源:
框架有堅實的社區嗎?
這大概是最難評估的,因為社區沒辦法用什麼數字直接估算。你或許可以用 GitHub 的星星、或是 npm 的下載量來看,但有時最好的辦法,是找看看一些論壇、或和其他開發者聊聊看。這不只是社區大小的問題而已,你還需要看看社區多麼熱情和包容、以及文件多好讀。
Web 的看法
在選框架這件事,不要只聽我們的話:網路上也有不少的討論。例如說,維基媒體基金會最近就選了 Vue 作為他們的前端框架,還發表了相關的請求意見稿。請求意見稿的作者 Eric Gardner 花了不少時間,概述維基媒體基金會的需求、還有為什麼他認為這個框架,對開發團隊有益。這個請求意見稿,是你在研究要使用什麼框架時,需要考量什麼的一個好例子。
State of JavaScript survey 也是有用的 JavaScript 開發者反饋集合。它包含了很多 JavaScript 相關的要點、包括有關框架使用情況、還有開發者對各框架看法的數據。網站也有數年的可用數據,以便了解框架的消長。
Vue 的開發團隊也寫了有關 Vue 與其他框架的詳盡比較。如同他們自知,這種比較可能會有一些偏見,但這仍然是一種寶貴的資源。
前端框架的替代
如果在尋找能夠加速開發的工具、卻又發現專案不需要前端 JavaScript 框架的話,可以試試其他用於構建 Web 的解決方案:
- 內容管理系統(content management system)
- 伺服器渲染(Server-side rendering)
- 靜態網站產生器(static site generator)
內容管理系統
伺服器渲染
伺服器端渲染(Server-side rendering, SSR)是由伺服器負責渲染單頁應用的程式架構,與構建 JavaScript 程式中最常見,最直接的用戶端渲染(client-side rendering)相對。伺服器端渲染只單純傳送 HTML 檔案,所以對用戶端更友善;但設置起來就比用戶端渲染程式難得多。
本模塊的所有用戶端渲染框架,都有相對應的伺服器端渲染。像是 React 的 Next.js、Vue 的 Nuxt.js(對這的確很教人困惑,不過兩者沒有關係)、Ember 的 FastBoot、Angular 的 Angular Universal。
備註:某些伺服器端渲染的解決方案,是由社區編寫和維護;但也有「官方」的解決方案,是由框架維護者提供的。
靜態網站產生器
靜態網站產生器是個可以給網站生成多個網頁的程式 ── 這包括相對應的 CSS 或 JavaScript── 以便在任何地方發布。發布主機可以是 GitHub pages 分支、或著 Netlify 實體、抑或著是私人的伺服器。這種方法有很多優點,主要是性能方面(用戶端收到網頁時,已經載入了整個網頁,所以不需要執行 JavaScript)與安全性(靜態網站的攻擊因為變少了)。靜態網站還是能在需要時用上 JavaScript,但靜態網站並不依賴 JavaScript。一如其他工具,靜態網站產生器需要點時間去搞懂。這可能是開發過程的障礙。
靜態網站的獨立網頁,要多少就能有多少。如同框架能加快開發用戶端 JavaScript 程式一般,靜態網站產生器也能快速產生 HTML 檔案(要不然,你本來要自己寫的)。靜態網站產生器也像框架一樣,能允許用戶給網頁撰寫組件;並在最後建立頁面時,把組件都結合起來。以靜態網站產生器而言,組件被稱為樣板(template)。靜態網站產生器構建的網頁,甚至可以作為框架應用程式的宿主:比方說你可以完成「如果希望用戶造訪某個特定頁面時,啟動 React 程式」這樣的需求。
靜態網站產生器已經存在好一段時間了,但他們最近在 Web 歷史中再度復興起來。現在有一些強大的選擇,像是 Hugo、Jekyll、Eleventy、Gatsby。
如果想深入理解靜態網站產生器的概念,看一下 Tatiana Mac 的 Beginner's guide to Eleventy。在該系列的第一篇文章中,她解釋了什麼是靜態網站生成器,以及它與發布 Web 內容的其他方式之間的關係。