框架主要功能
每個主要的 JavaScript 框架在更新 DOM、處理瀏覽器事件以及提供愉快的開發者體驗方面都有不同的方法。本文將探討“四大”框架的主要特性,從宏觀角度審視框架的工作方式以及它們之間的差異。
| 預備知識 | 熟悉核心 HTML、CSS 和 JavaScript 語言。 |
|---|---|
| 學習成果 | 瞭解 JavaScript 框架提供的主要特性。 |
領域特定語言
大多數框架允許你使用領域特定語言(DSL)來構建應用程式。特別是,React 推廣了使用 JSX 來編寫其元件,而 Ember 則使用 Handlebars。與 HTML 不同,這些語言知道如何讀取資料變數,這些資料可以用於簡化 UI 的編寫過程。
Angular 應用程式經常大量使用 TypeScript。TypeScript 不涉及使用者介面的編寫,但它是一種領域特定語言,與普通的 JavaScript 有顯著差異。
DSL 不能直接被瀏覽器讀取;它們必須首先轉換為 JavaScript 或 HTML。框架工具通常包含處理此步驟所需的工具,或者可以調整以包含此步驟。雖然不使用這些領域特定語言也可以構建框架應用程式,但接受它們將簡化你的開發過程,並使你更容易從這些框架社群中獲得幫助。
JSX
JSX 代表 JavaScript 和 XML,是 JavaScript 的一個擴充套件,將類似 HTML 的語法帶入 JavaScript 環境。它由 React 團隊發明,用於 React 應用程式,但也可用於開發其他應用程式,例如 Vue 應用程式。
以下是一個簡單的 JSX 示例
const subject = "World";
const header = (
<header>
<h1>Hello, {subject}!</h1>
</header>
);
此表示式表示一個 HTML <header> 元素,其中包含一個 <h1> 元素。{subject} 周圍的 curly braces(花括號)告訴應用程式讀取 subject 常量的值並將其插入到我們的 <h1> 中。
當與 React 一起使用時,前面程式碼片段中的 JSX 將被編譯成這樣
const subject = "World";
const header = React.createElement(
"header",
null,
React.createElement("h1", null, "Hello, ", subject, "!"),
);
當最終由瀏覽器渲染時,上述程式碼片段將生成如下所示的 HTML
<header>
<h1>Hello, World!</h1>
</header>
Handlebars
Handlebars 模板語言並非 Ember 應用程式所特有,但它在 Ember 應用程式中被大量使用。Handlebars 程式碼類似於 HTML,但它可以從其他地方獲取資料。這些資料可以影響應用程式最終構建的 HTML。
與 JSX 類似,Handlebars 使用花括號來注入變數的值。Handlebars 使用雙花括號,而不是單花括號。
給定這個 Handlebars 模板
<header>
<h1>Hello, {{subject}}!</h1>
</header>
以及這些資料
{
"subject": "World"
}
Handlebars 將構建如下 HTML
<header>
<h1>Hello, World!</h1>
</header>
TypeScript
TypeScript 是 JavaScript 的一個超集,這意味著它擴充套件了 JavaScript——所有 JavaScript 程式碼都是有效的 TypeScript,但反之則不然。TypeScript 因其允許開發者對其程式碼強制執行嚴格性而非常有用。例如,考慮一個函式 add(),它接受整數 a 和 b 並返回它們的和。
在 JavaScript 中,該函式可以這樣編寫
function add(a, b) {
return a + b;
}
這段程式碼對於習慣 JavaScript 的人來說可能微不足道,但它仍然可以更清晰。JavaScript 允許我們使用 + 運算子將字串連線起來,所以如果 a 和 b 是字串,這個函式在技術上仍然可以工作——只是可能不會給你期望的結果。如果我們只想允許數字傳遞到這個函式中怎麼辦?TypeScript 使這成為可能
function add(a: number, b: number) {
return a + b;
}
這裡每個引數後面的 : number 告訴 TypeScript a 和 b 都必須是數字。如果我們要使用這個函式並將 '2' 作為引數傳入,TypeScript 將在編譯期間丟擲錯誤,我們將被迫修復錯誤。我們可以編寫自己的 JavaScript 來為我們丟擲這些錯誤,但這將使我們的原始碼顯著地更加冗長。讓 TypeScript 為我們處理這些檢查可能更有意義。
編寫元件
如上一課所述,大多數框架都有某種元件模型。React 元件可以用 JSX 編寫,Ember 元件用 Handlebars 編寫,Angular 和 Vue 元件用稍微擴充套件 HTML 的模板語法編寫。
無論它們對元件如何編寫的看法如何,每個框架的元件都提供了一種方法來描述它們可能需要的外部屬性、元件應管理的內部狀態以及使用者可以在元件標記上觸發的事件。
本節其餘程式碼片段將以 React 為例,並使用 JSX 編寫。
屬性
屬性,或稱 props,是元件渲染所需的外部資料。假設你正在為一家線上雜誌構建網站,並且你需要確保每位撰稿人都能獲得他們的作品署名。你可能會建立一個 AuthorCredit 元件來搭配每篇文章。這個元件需要顯示作者的肖像和關於他們的簡短署名。為了知道要渲染什麼影像和列印什麼署名,AuthorCredit 需要接受一些 props。
這個 AuthorCredit 元件的 React 表示可能看起來像這樣
function AuthorCredit(props) {
return (
<figure>
<img src={props.src} alt={props.alt} />
<figcaption>{props.byline}</figcaption>
</figure>
);
}
{props.src}、{props.alt} 和 {props.byline} 表示我們的 props 將被插入到元件中的位置。要渲染這個元件,我們會在我們希望它渲染的地方(很可能在另一個元件內部)編寫這樣的程式碼
<AuthorCredit
src="./assets/zelda.png"
alt="Portrait of Zelda Schiff"
byline="Zelda Schiff is editor-in-chief of the Library Times."
/>
這最終將在瀏覽器中渲染以下 <figure> 元素,其結構由 AuthorCredit 元件定義,其內容由 AuthorCredit 元件呼叫中包含的 props 定義
<figure>
<img src="assets/zelda.png" alt="Portrait of Zelda Schiff" />
<figcaption>Zelda Schiff is editor-in-chief of the Library Times.</figcaption>
</figure>
狀態
我們在上一章中討論了狀態的概念——一個健壯的狀態處理機制是有效框架的關鍵,每個元件都可能有需要控制其狀態的資料。只要元件在使用中,此狀態將以某種方式持久存在。與 props 類似,狀態可以用來影響元件的渲染方式。
舉個例子,考慮一個計算按鈕被點選次數的按鈕。這個元件應該負責跟蹤它自己的計數狀態,並且可以這樣編寫
function CounterButton() {
const [count] = useState(0);
return <button>Clicked {count} times</button>;
}
useState() 是一個 React Hook,它給定一個初始資料值,將在該值更新時跟蹤它。程式碼將最初在瀏覽器中這樣渲染
<button>Clicked 0 times</button>
useState() 呼叫以一種健壯的方式在應用程式中跟蹤 count 值,而無需你親自編寫程式碼來完成此操作。
事件
為了實現互動性,元件需要能夠響應瀏覽器事件,以便我們的應用程式能夠響應使用者。每個框架都提供自己的語法來監聽瀏覽器事件,這些語法引用了等效原生瀏覽器事件的名稱。
在 React 中,監聽 click 事件需要一個特殊的屬性 onClick。讓我們更新上面的 CounterButton 程式碼,使其能夠計數點選次數
function CounterButton() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>Clicked {count} times</button>
);
}
在這個版本中,我們使用了額外的 useState() 功能來建立一個特殊的 setCount() 函式,我們可以呼叫它來更新 count 的值。我們在 onClick 事件處理程式中呼叫這個函式,將 count 設定為其當前值加一。
元件樣式
每個框架都提供了一種為元件或整個應用程式定義樣式的方法。儘管每個框架定義元件樣式的方法略有不同,但它們都提供了多種方式來完成此操作。透過新增一些輔助模組,你可以使用 Sass 或 Less 為你的框架應用程式設定樣式,或者使用 PostCSS 轉譯你的 CSS 樣式表。
處理依賴
所有主要框架都提供了處理依賴項的機制——在其他元件內部使用元件,有時具有多個層級。與其他特性一樣,具體機制在不同框架之間會有所不同,但最終結果是相同的。元件通常使用標準的 JavaScript 模組語法,或至少類似的東西,將元件匯入到其他元件中。
元件中的元件
基於元件的 UI 架構的一個關鍵好處是元件可以組合在一起。就像你可以在 HTML 標籤內部編寫 HTML 標籤來構建網站一樣,你也可以在其他元件內部使用元件來構建 Web 應用程式。每個框架都允許你編寫利用(並因此依賴於)其他元件的元件。
例如,我們的 AuthorCredit React 元件可能會在 Article 元件內部使用。這意味著 Article 需要匯入 AuthorCredit。
import AuthorCredit from "./components/AuthorCredit";
完成此操作後,AuthorCredit 可以在 Article 元件內部這樣使用
<Article>
<AuthorCredit />
</Article>
依賴注入
實際應用程式通常涉及具有多層巢狀的元件結構。一個深層巢狀的 AuthorCredit 元件,出於某種原因,可能需要來自我們應用程式最根層的資料。
假設我們正在構建的雜誌網站結構如下
<App>
<Home>
<Article>
<AuthorCredit {/* props */} />
</Article>
</Home>
</App>
我們的 App 元件擁有 AuthorCredit 元件所需的資料。我們可以重寫 Home 和 Article,讓它們知道如何向下傳遞 props,但這可能會很繁瑣,如果我們的資料來源和目的地之間有許多許多層級。這也是多餘的:Home 和 Article 實際上不使用作者的肖像或署名,但如果我們要將這些資訊傳遞到 AuthorCredit 中,我們將需要更改 Home 和 Article 以適應它。
透過多層元件傳遞資料的問題稱為 props 鑽取,對於大型應用程式來說並不理想。
為了避免 props 鑽取,框架提供了稱為依賴注入的功能,這是一種將特定資料直接傳遞給需要它的元件,而無需經過中間層級的方法。每個框架以不同的名稱和方式實現依賴注入,但效果最終是相同的。
Angular 將此過程稱為依賴注入;Vue 具有provide() 和 inject() 元件方法;React 具有Context API;Ember 透過服務共享狀態。
生命週期
在框架的語境中,元件的生命週期是一個元件從它被新增到 DOM 並由瀏覽器渲染(通常稱為掛載)到它從 DOM 中移除(通常稱為解除安裝)所經歷的一系列階段。每個框架對這些生命週期階段的命名不同,並且並非所有框架都允許開發者訪問相同的階段。所有框架都遵循相同的通用模型:它們允許開發者在元件掛載時、渲染時、解除安裝時以及介於這些階段之間的許多階段執行某些操作。
渲染階段是最關鍵的,因為它在使用者與應用程式互動時重複的次數最多。每當瀏覽器需要渲染新的內容時,無論是瀏覽器中現有內容的新增、刪除還是編輯,它都會執行。
此 React 元件生命週期圖提供了對該概念的總體概述。
渲染元素
與生命週期一樣,框架在如何渲染應用程式方面也採取了不同但相似的方法。所有框架都跟蹤瀏覽器 DOM 的當前渲染版本,並且每個框架在應用程式中的元件重新渲染時對 DOM 如何改變做出略微不同的決策。由於框架為你做出這些決策,你通常不需要親自與 DOM 互動。這種從 DOM 中抽象出來的行為比你自己更新 DOM 更復雜,也更佔用記憶體,但沒有它,框架就無法讓你以它們聞名的宣告性方式進行程式設計。
虛擬 DOM 是一種方法,其中有關瀏覽器 DOM 的資訊儲存在 JavaScript 記憶體中。你的應用程式更新此 DOM 副本,然後將其與“真實”DOM(即實際為使用者渲染的 DOM)進行比較,以決定要渲染什麼。應用程式構建一個“diff”來比較更新後的虛擬 DOM 和當前渲染的 DOM 之間的差異,並使用該 diff 將更新應用到真實 DOM。React 和 Vue 都使用虛擬 DOM 模型,但它們在 diffing 或渲染時並未應用完全相同的邏輯。
你可以在 React 文件中閱讀更多關於虛擬 DOM 的資訊。
增量 DOM 與虛擬 DOM 類似,因為它構建一個 DOM diff 來決定要渲染什麼,但不同之處在於它不會在 JavaScript 記憶體中建立 DOM 的完整副本。它忽略了 DOM 中不需要更改的部分。Angular 是本模組中迄今為止討論的唯一使用增量 DOM 的框架。
你可以在 Auth0 部落格上閱讀更多關於增量 DOM 的資訊。
Glimmer VM 是 Ember 獨有的。它既不是虛擬 DOM 也不是增量 DOM;它是一個獨立的過程,透過它 Ember 的模板被轉譯成一種比 JavaScript 更容易和更快讀取的“位元組碼”。
路由
正如上一章提到的,路由是 Web 體驗的重要組成部分。為了避免在具有許多檢視的足夠複雜的應用程式中出現中斷的體驗,本模組中介紹的每個框架都提供了一個(或多個)庫,幫助開發者在其應用程式中實現客戶端路由。
測試
所有應用程式都受益於測試覆蓋,這確保你的軟體繼續按預期執行,Web 應用程式也不例外。每個框架的生態系統都提供有助於編寫測試的工具。測試工具不內置於框架本身,但用於生成框架應用程式的命令列介面工具使你能夠訪問適當的測試工具。
每個框架的生態系統都有廣泛的工具,包括單元測試和整合測試功能。
Testing Library 是一套測試實用工具,為許多 JavaScript 環境提供工具,包括 React、Vue 和 Angular。Ember 文件涵蓋了 Ember 應用程式的測試。
這是使用 React Testing Library 編寫的 CounterButton 的一個快速測試——它測試了許多方面,例如按鈕的存在,以及按鈕在被點選 0、1 和 2 次後是否顯示正確的文字
import { fireEvent, render, screen } from "@testing-library/react";
import CounterButton from "./CounterButton";
it("Renders a semantic button with an initial state of 0", () => {
render(<CounterButton />);
const btn = screen.getByRole("button");
expect(btn).toBeInTheDocument();
expect(btn).toHaveTextContent("Clicked 0 times");
});
it("Increments the count when clicked", () => {
render(<CounterButton />);
const btn = screen.getByRole("button");
fireEvent.click(btn);
expect(btn).toHaveTextContent("Clicked 1 times");
fireEvent.click(btn);
expect(btn).toHaveTextContent("Clicked 2 times");
});
總結
至此,你應該對使用框架建立應用程式時將實際使用的語言、特性和工具有了更深入的瞭解。我確信你對開始動手編寫程式碼充滿熱情,而這正是你接下來要做的!