Web 框架解決了什麼問題以及如何不使用它們(第 1 部分)

已發表: 2022-03-10
快速總結 ↬在本文中,Noam Rosenthal 深入探討了跨框架常見的一些技術特性,並解釋了一些不同的框架如何實現它們以及它們的成本。

我最近對將框架與普通 JavaScript 進行比較變得非常感興趣。 它是在我在一些自由職業項目中使用 React 感到沮喪之後開始的,並且我最近更熟悉作為規範編輯器的 Web 標準。

我很想知道這些框架之間的共性和差異是什麼,作為更精簡的替代方案,Web 平台必須提供什麼,以及它是否足夠。 我的目標不是抨擊框架,而是了解成本和收益,確定是否存在替代方案,看看我們是否可以從中學習,即使我們決定使用框架。

在第一部分中,我將深入探討跨框架的一些常見技術特性,以及一些不同的框架如何實現它們。 我還將研究使用這些框架的成本。

框架

我選擇了四個框架來研究:React,是當今占主導地位的框架,以及三個聲稱做事與 React 不同的新競爭者。

  • 反應
    “React 讓創建交互式 UI 變得輕鬆。 聲明式視圖使您的代碼更可預測且更易於調試。”
  • SolidJS
    “Solid 遵循與 React 相同的理念……但是它有一個完全不同的實現,它放棄了使用虛擬 DOM。”
  • 苗條
    “Svelte 是一種構建用戶界面的全新方法……構建應用程序時發生的編譯步驟。 Svelte 沒有使用虛擬 DOM 差異等技術,而是編寫了在您的應用程序狀態發生變化時通過手術更新 DOM 的代碼。”
  • 點亮
    “在 Web Components 標準的基礎上,Lit 添加了……反應性、聲明性模板和一些深思熟慮的功能。”

總結一下這些框架對它們的區別的看法:

  • React 使用聲明式視圖讓構建 UI 變得更容易。
  • SolidJS 遵循 React 的理念,但使用了不同的技術。
  • Svelte 對 UI 使用編譯時方法。
  • Lit 使用現有標準,並添加了一些輕量級功能。

框架解決什麼

框架本身提到了聲明性、反應性和虛擬 DOM 等詞。 讓我們深入了解這些含義。

聲明式編程

聲明式編程是一種在不指定控制流的情況下定義邏輯的範例。 我們描述了結果需要是什麼,而不是我們需要採取哪些步驟。

在聲明式框架的早期,大約在 2010 年,DOM API 更加裸露和冗長,使用命令式 JavaScript 編寫 Web 應用程序需要大量樣板代碼。 那時“模型-視圖-視圖模型”(MVVM)的概念變得流行起來,當時開創性的 Knockout 和 AngularJS 框架提供了一個 JavaScript 聲明層來處理庫內部的複雜性。

MVVM 在今天並不是一個廣泛使用的術語,它在某種程度上是舊術語“數據綁定”的變體。

數據綁定

數據綁定是一種聲明性方式來表達數據如何在模型和用戶界面之間同步。

所有流行的 UI 框架都提供了某種形式的數據綁定,它們的教程都從一個數據綁定示例開始。

這是 JSX 中的數據綁定(SolidJS 和 React):

 function HelloWorld() { const name = "Solid or React"; return ( <div>Hello {name}!</div> ) }

Lit 中的數據綁定:

 class HelloWorld extends LitElement { @property() name = 'lit'; render() { return html`<p>Hello ${this.name}!</p>`; } }

Svelte 中的數據綁定:

 <script> let name = 'world'; </script> <h1>Hello {name}!</h1>

反應性

反應性是一種表達變化傳播的聲明性方式。

當我們有一種方法來聲明性地表達數據綁定時,我們需要一種有效的方法讓框架傳播更改。

React 引擎將渲染結果與之前的結果進行比較,並將差異應用於 DOM 本身。 這種處理變更傳播的方式稱為虛擬 DOM。

在 SolidJS 中,這通過它的存儲和內置元素來更明確地完成。 例如, Show元素將跟踪內部發生的變化,而不是虛擬 DOM。

在 Svelte 中,會生成“反應式”代碼。 Svelte 知道哪些事件會導致更改,它會生成簡單的代碼,在事件和 DOM 更改之間劃清界限。

在 Lit 中,反應性是使用元素屬性完成的,本質上依賴於 HTML 自定義元素的內置反應性。

跳躍後更多! 繼續往下看↓

邏輯

當一個框架為數據綁定提供一個聲明式接口時,通過它的響應性實現,它還需要提供某種方式來表達一些傳統上以命令式編寫的邏輯。 邏輯的基本構建塊是“if”和“for”,所有主要框架都提供了這些構建塊的一些表達。

條件句

除了綁定數字和字符串等基本數據外,每個框架都提供“條件”原語。 在 React 中,它看起來像這樣:

 const [hasError, setHasError] = useState(false); return hasError ? <label>Message</label> : null; … setHasError(true);

SolidJS 提供了一個內置的條件組件Show

 <Show when={state.error}> <label>Message</label> </Show>

Svelte 提供了#if指令:

 {#if state.error} <label>Message</label> {/if}

在 Lit 中,您將在render函數中使用顯式三元運算:

 render() { return this.error ? html`<label>Message</label>`: null; }

列表

另一個常見的框架原語是列表處理。 列表是 UI 的關鍵部分——聯繫人列表、通知等——為了有效地工作,它們需要具有響應性,而不是在一個數據項更改時更新整個列表。

在 React 中,列表處理看起來像這樣:

 contacts.map((contact, index) => <li key={index}> {contact.name} </li>)

React 使用特殊的key屬性來區分列表項,並確保整個列表不會被每個渲染替換。

在 SolidJS 中,使用了forindex內置元素:

 <For each={state.contacts}> {contact => <DIV>{contact.name}</DIV> } </For>

在內部,SolidJS 使用自己的 store 以及forindex來決定在項目更改時更新哪些元素。 它比 React 更明確,允許我們避免虛擬 DOM 的複雜性。

Svelte 使用each指令,該指令根據其更新程序進行轉譯:

 {#each contacts as contact} <div>{contact.name}</div> {/each}

Lit 提供了一個repeat函數,其工作方式類似於 React 的基於key的列表映射:

 repeat(contacts, contact => contact.id, (contact, index) => html`<div>${contact.name}</div>`

組件模型

超出本文範圍的一件事是不同框架中的組件模型以及如何使用自定義 HTML 元素處理它。

注意這是一個很大的主題,我希望在以後的文章中介紹它,因為這篇文章會太長。 :)

成本

框架提供聲明性數據綁定、控制流原語(條件和列表)以及傳播更改的反應機制。

它們還提供其他主要內容,例如重用組件的方法,但這是另一篇文章的主題。

框架有用嗎? 是的。 它們為我們提供了所有這些方便的功能。 但這是正確的問題嗎? 使用框架是有代價的。 讓我們看看這些成本是多少。

捆綁大小

在查看捆綁包大小時,我喜歡查看縮小的非 Gzip 大小。 這是與 JavaScript 執行的 CPU 成本最相關的大小。

  • ReactDOM 大約 120 KB。
  • SolidJS 大約 18 KB。
  • 點亮約為 16 KB。
  • Svelte 大約 2 KB,但生成的代碼大小不同。

似乎今天的框架在保持包大小方面比 React 做得更好。 虛擬 DOM 需要大量的 JavaScript。

構建

不知何故,我們習慣了“構建”我們的網絡應用程序。 如果不設置 Node.js 和 Webpack 之類的捆綁器,處理 Babel-TypeScript 啟動包中最近的一些配置更改,以及所有這些爵士樂,就不可能啟動前端項目。

框架的表現力越強,包大小越小,構建工具和編譯時間的負擔就越大。

Svelte 聲稱虛擬 DOM 是純粹的開銷。 我同意,但也許“構建”(如 Svelte 和 SolidJS)和自定義客戶端模板引擎(如 Lit)也是一種不同類型的純開銷?

調試

建造和轉譯帶來了不同的成本。

我們在使用或調試 Web 應用程序時看到的代碼與我們編寫的完全不同。 我們現在依靠不同質量的特殊調試工具對網站上發生的事情進行逆向工程,並將其與我們自己代碼中的錯誤聯繫起來。

在 React 中,調用堆棧永遠不是“你的”——React 為你處理調度。 當沒有錯誤時,這很有效。 但是嘗試確定無限循環重新渲染的原因,您將陷入痛苦的世界。

在 Svelte 中,庫本身的包大小很小,但是您將發布和調試一大堆神秘的生成代碼,這些代碼是 Svelte 的響應性實現,根據您的應用程序的需求進行定制。

使用 Lit,它與構建無關,但要有效地調試它,您必須了解它的模板引擎。 這可能是我對框架持懷疑態度的最大原因。

當您尋找自定義的聲明式解決方案時,您最終會遇到更痛苦的命令式調試。 本文檔中的示例使用 Typescript 進行 API 規範,但代碼本身不需要轉譯。

升級

在本文檔中,我查看了四個框架,但框架的數量我數不勝數(AngularJS、Ember.js 和 Vue.js 等等)。 你能指望框架、它的開發人員、它的思想共享和它的生態系統隨著它的發展而為你工作嗎?

比修復自己的錯誤更令人沮喪的一件事是必須為框架錯誤找到解決方法。 比框架錯誤更令人沮喪的一件事是當您將框架升級到新版本而不修改代碼時發生的錯誤。

誠然,這個問題也存在於瀏覽器中,但是當它發生時,它會發生在每個人身上,並且在大多數情況下,修復或發布的解決方法迫在眉睫。 此外,本文檔中的大部分模式都基於成熟的 Web 平台 API; 並不總是需要緊跟前沿。

概括

我們更深入地了解了框架試圖解決的核心問題以及它們如何解決這些問題,重點關注數據綁定、反應性、條件和列表。 我們還查看了成本。

在第 2 部分中,我們將了解如何在不使用框架的情況下解決這些問題,以及我們可以從中學到什麼。 敬請關注!

特別感謝以下人員的技術審查:Yehonatan Daniv、Tom Bigelajzen、Benjamin Greenbaum、Nick Ribal 和 Louis Lazaris。