前端框架 React 和 Svelte 的基礎比較

語言: CN / TW / HK

在 JavaScript 前端開發框架中,Svelte 算是一個新來的攪局者,在網上我們已經聽到很多關於 Svelte 的嗶嗶。因此我決定試試這個傢伙,順便跟 React 做個簡單的比較。

本文將展示 Svelte 和 React 在構建一個基礎應用的差異,其中涉及到的內容包括:

  • 元件結構
  • 狀態初始化
  • 屬性傳遞
  • 狀態向上傳遞
  • 事件偵聽
  • 動態樣式

還有很多其他方面的內容需要討論,例如 按需渲染 和 生命週期 等其他炫酷的概念。限於篇幅,這篇文章還是聚焦在基礎使用上吧。

準備工作

在繼續往下閱讀之前,你應該準備好如下環境:

Svelte 與 React

Svelte 和 React.js 兩者都是基於元件的 JavaScript 框架,主要用於 Web 應用的開發。最主要的區別是 Svelte 沒有使用虛擬 DOM。Svelte 在構建的時候就將程式碼編譯成 Vanilla JS 程式碼,而 React 在執行時解釋程式碼。

Svelte 文件寫道:

‎Svelte 是一種全新的構建 Web 應用的方法。諸如 React 和 Vue 這類傳統的框架,它們的大部分工作都在瀏覽器上執行,而 Svelte 在構建應用的過程做就了大量的工作。

‎Svelte 沒有使用虛擬 DOM 技術,而是當應用狀態發生變化時,通過程式碼如手術般的更新 DOM。‎

酷!但是這些底層的細節對我來說並不重要。我只想從開發人員的角度看看,在使用 Svelte 和 React 開發應用程式時,感覺好嗎?有趣嗎?直觀嗎?

開工!

建立應用腳手架

在這篇文章中,我們將建立一個很小的 Web 應用,產品經理給這個應用確定瞭如下需求:

  • 三個元件,分別是:App 、Heading 和 Button
  • 當點選 Button 時,Heading 會更新顯示點選的次數
  • 每次點選 Button 時,Button 自身的顏色會跟著變化

首先使用如下命令在你電腦上建立一個新的目錄,暫且命名為 svelte-react

mkdir svelte-react
cd svelte-react

接著分別建立 Svelte 和 React 的應用模板並執行。這裡 Svelte 的初始步驟比 React 多了一步,此外 Svelte 預設埠是 5000 ,而 React 是 3000 。

Svelte

開啟終端視窗,執行如下命令:

npx degit sveltejs/template svelte-test
cd svelte-test
npm install
npm run dev

React

開啟第二個終端視窗,進入剛建好的 svelte-react 目錄,執行命令:

npx create-react-app react-test
cd svelte-react
npm start

你會發現 Svelte 的命令執行快得多,因為你不是真正在執行一個工具,而是克隆一個專案模板。

構建應用元件

執行完上述命令後,你會注意到 Svelte 和 React 各自生成很多很多的檔案,感興趣的話,可以隨便瀏覽看看這些生成的檔案。

不管是 Svelte 和 React ,都是把元件原始碼放到 src 資料夾下,Svelte 專案主要是一些副檔名為 svelte 的檔案,而 React 專案則是一些 .js 的檔案。

兩個專案都有一個 App 元件,分別是 App.svelte 和 App.js 。用你喜好的編輯器分別開啟這兩個檔案,清空它們,我們從頭開始。

元件結構

Svelte

和 React 元件不同的是,Svelte 的程式碼更像是以前我們在寫 HTML、CSS 和 JavaScript 一樣。

所有的 JavaScript 程式碼都位於 Svelte 檔案頂部的 <script></script> 標籤當中。

然後是 HTML 程式碼,你還可以在 <style></style> 標籤中編寫樣式程式碼。有趣的是,元件中的樣式程式碼只對當前元件有效。這意味著在元件中為 <p> 標籤編寫的樣式不會影響到其他元件中的 <p> 元素。

接下來我們開始編寫 App.svelte,首先刪空檔案內容,然後新增一個空的 <script> 標籤:

<script>
</script>

我們將在這個標籤中編寫大部分元件程式碼。

React

在 React 專案中,開啟 App.js 檔案,清空所有內容,然後新增如下程式碼:

function App() {

}

export default App;

這幾行程式碼建立並輸出了一個最基礎的函式式元件,名為 App()https://doc.rust-lang.org/book/ch01-02-hello-world.html 。注意到這裡還有另外一個不同之處就是 —— Svelte 無需輸出元件。

Imports

前面我們介紹過這個應用包含三個元件: App, HeadingButton。不管是 Svelte 還是 React ,Heading 和 Button 元件都被引入到 App 中,這樣就可以被當成 App 的子元件使用。我們將在後面繼續編寫這三個元件的程式碼,但現在你只需要知道,構建 App 元件時需要引入其他兩個元件。

Svelte

Svelte 需要在 <script> 使用 import 語句進行元件引入,編輯 App.svelte 檔案新增兩個 import 語句:

<script>
  import Button from './Button.svelte';
  import Heading from './Heading.svelte';
</script>

React

React 的 import 語句位於檔案的頂部,置於所有的函式或者類定義之前。在 App.js 最頂部,App() 函式之前,新增如下程式碼:

import Heading from './Heading.js';
import Button from './Button.js';
import { useState } from 'react';

在這裡,React 同時引入了 userState 鉤子,因為 App 是一個有狀態的元件。而 Svelte 不需要這個東西。

狀態初始化

App 是一個有狀態的元件,它有兩個狀態值分別是 color  count

color 表示按鈕的顏色,這個值作為一個屬性傳遞給 Button 元件,並且它在每次點選按鈕的時候改變。其初始值是 #000000,即為黑色。

count 代表按鈕點選的次數,其初始值為 0。

Svelte

在 Svelte 中,狀態等同於變數賦值,在 import 語句下方,<script> 標籤之前新增如下狀態定義:

let count = 0;
let color = '#000000';

Svelte 同時提供了名為”反應式宣告“ 的概念,用來重新計算狀態值,你不一定必須用這個,但如果狀態值依賴於其他可能更改的狀態,這時候就很方便。

需要注意的是在 Svelte 中是通過狀態變數的賦值來實現 DOM 更新的。如果狀態包含陣列或者物件,當對陣列使用類似 .push() 方法並不會觸發 DOM 更新。Svelte 提供了一個詳細文件來介紹這個問題。

React

現在已經引入了 useState 鉤子,所以只需要讓它工作起來即可。

App.js App() 函式中新增如下狀態宣告:

const [count, setCount] = useState(0);
const [color, setColor] = useState('#000000');

上述程式碼建立一個名為 count的狀態變數,其初始值為 0,以及一個用來更新值的函式名為 setCount()。同樣的,React 建立了另一個狀態變數 color 初始值為 #000000以及名為 setColor()的更新函式。從這點來看,Svelte 的狀態初始化方法要簡單易懂得多。

元件渲染和屬性傳遞

兩個專案我們都是要建立一個由 <main> 元素構建的使用者介面,該元素包含兩個巢狀的元件 Heading 和 Button.

App 元件傳遞屬性給兩個子元件。Heading 元件接收 count 狀態值,Button 元件接收 color 狀態值,此外還有一個名為 handleClick() 的事件處理函式。

Svelte

Svelte 使用它自己的模板語言來建立使用者介面,而 React 使用 JSX 。Svelte 模板語言跟寫 HTML 沒什麼兩樣。接下來只需在 <script> 標籤結束後開始編寫。

拷貝如下 <main> 部分程式碼到 App.svelte 檔案中,形如:

<script>
  ...
</script>

<main>
  <Heading count={count} />
  <Button color={color} handleClick={handleClick} />
</main>

React

回到 App.js, 將如下程式碼拷貝到你的 App() 函式中狀態宣告部分的下方:

return (
  <main>
    <Heading count={count} />     
    <Button color={color} handleClick={handleClick} />
  </main>
)

該程式碼從 App() 函式中返回 UI 介面的 JSX

這裡 Svelte 和 React 的做法都很類似,屬性的傳遞也幾乎相同。而 Svelte 的模板看起來跟 React 的 JSX 很像。

如果你是一個對 Svelte 充滿好奇的 React 開發人員,在屬性傳遞上 Svelte 沒有什麼新奇之處。而在接收屬性時 Svelte 有點點不一樣,後面將進行介紹。

狀態向上傳遞

為了讓這個應用正常工作,每次點選按鈕時,必須讓 App 元件的 count 狀態值增1。因此需要一個機制來將資料從子元件傳遞給父元件。

前面已經通過將 handleClick() 函式作為屬性傳遞給 Button 元件。

接下來馬上要開始編寫的這個屬於 App 元件的函式。當把它作為屬性傳遞給 Button 子元件,Button 元件就能在每次被點選時呼叫這個函式。這就是 App 元件能響應其子元件狀態變更的原因。

handleClick() 這個函式負責用來更新 App 元件的count 和 color 狀態值。

Svelte

在 App.svelte 中編寫 handleClick 函式程式碼如下:

const colors = ['#00ff00', '#ff0000', '#0000ff'];

let handleClick = () => {
  count++;
  color = colors[Math.floor(Math.random() * 3)];
}

React

在 App.js 中編寫 handleClick 函式程式碼如下:

const colors = ['#00ff00', '#ff0000', '#0000ff'];

let handleClick = () => {
  setCount(count+1);
  setColor(colors[Math.floor(Math.random() * 3)]);
}

在 React 需要使用早先宣告的 setCount() 和 setColor() 方法來更新狀態值,而 Svelte 則可以直接更新。

現在我們可以開始編寫 Heading 元件了。

編寫 Heading 元件

Heading 元件顯示這個應用的標題以及點選計數器。這不是一個有狀態的元件,其接收狀態值 count來顯示按鈕點選次數。

在 Svelte 專案的 src  資料夾中建立一個名為 Heading.svelte 的檔案。

同樣的在 React 專案的 src 資料夾中建立新檔案 Heading.js.

接收屬性

Svelte

拷貝如下程式碼到 Heading.svelte 檔案:

<script>
  export let count;
</script>

<h1>Hello, I am a Svelte App!</h1>
<h2>The following button has been clicked {count} times.</h2>

請注意看上述程式碼中 <script> 裡的程式碼。這行程式碼告訴 Svelte 說,該元件將接收一個名為 count的屬性。

這樣就可以在 Heading 元件的 HTML 模板中直接顯示 count 這個屬性。

這個寫法稍微有點點奇怪,但在檔案頂部直接宣告屬性的方式看起來不錯,而且可以直接使用這個屬性。

React

切換到 Heading.js 檔案,拷貝如下內容到該檔案:

function Heading({ count }) {
  return (
    <div>
      <h1>Hello, I am a React App!</h1>
      <h2>The following button has been clicked {count} times.</h2>
    </div>
  )
}

export default Heading;

這段程式碼建立一個新的名為 Heading 函式式元件,該元件有一個引數 { count }, 這是從傳遞給元件的 props物件中提取出來的。

編寫 Button 元件

Button 元件在介面上顯示一個按鈕,同時接收兩個屬性,分別是用來定義顏色的 color和在點選時觸發的 handleClick()函式。

在 Svelte 專案的 src 資料夾中建立新檔案 Button.svelte.

在 React 專案的 src 資料夾中建立新檔案 Button.js.

事件偵聽

類似點選和其他滑鼠事件等互動式事件的偵聽上,Svelte 和 React 的做法有一些不同。

Svelte

拷貝如下程式碼到 Button.svelte:

<script>
  export let handleClick;
  export let color;
</script>

<button style="--color: {color}" on:click={handleClick}>
  Click me!
</button>

上述程式碼中兩個屬性都是在頂部的 <script> 標籤中定義的。

然後它建立了一個按鈕。請注意第 6 行程式碼的語法,忽略掉下一節要介紹的樣式部分,直接看按鈕點選的事件偵聽器,它跟以往使用的習慣不同。

Svelte 使用一個 on: 指令來給 DOM 元素新增事件偵聽器。Svelte 使用非常簡潔方法進行事件修改,甚至可以只在按鈕首次點選時觸發。更詳細的關鍵事件的觸發請閱讀 dispatch your own component events 這篇文件。

React

拷貝如下程式碼到 Button.js:

function Button({ color, handleClick }) {
  return (
    <button style={styles}  onClick={handleClick}>
      Click me!
    </button>
  )
}

export default Button;

如果服務依然執行中,將會看到這裡有報錯資訊,別擔心,下面我們將通過新增 styles 物件來可以解決這個問題。

上述程式碼建立一個名為 Button() 的函式式元件,同時接收一個引數 props, 引數包含兩個屬性 color  handleClickhandleClick() 函式在 handleClick 屬性上定義,可以在 JSX 上使用一個標準的 onClick 事件來觸發。

動態樣式

在這個應用中 Button 元件介紹一個顏色值作為屬性,該顏色值就是按鈕的背景色。

Svelte

Svelte 的動態樣式沒有我期望的那麼直接。

很不幸,不能直接在 <style> 標籤中使用屬性值。不過可以使用元件的 HTML 作為在 JavaScript 和 CSS 之間通訊的方法。

 Button 元件 Button.svelte 的 HTML 程式碼下方增加如下程式碼:

<style>
  button {
    color: white;
    background-color: var(--color);
  }
</style>

background-color 樣式屬性不能直接引用 color 屬性的值,它引用的是一個名為 color的樣式變數,這個樣式變數在前面的 HTML 程式碼中通過 style="--color: {color}" 進行定義。

這個做法有一點點笨拙,但考慮到這個樣式僅在元件內有效,我們也是可以接受的。當然了,也可以定義全域性樣式,具體請閱讀 global CSS 這篇文件。

React

在 React 中可以有很多種方法給元件新增樣式。直接在元素上編寫樣式是最常用的方法。

要在 JSX 中使用內嵌樣式,可以使用樣式建立一個物件,然後賦值給元素的 style 屬性,剩下的部分前面已經實現過了。

 Button() 函式中的 return 語句前面新增如下程式碼來建立 styles 物件:

const styles = {
  backgroundColor: color,
  color: '#ffffff'
}

測試應用

儲存所有檔案,如果應用還沒有啟動,那現在就各自啟動服務 ( Svelte : npm run dev, React : npm start)。然後開啟瀏覽器的兩個 Tab 分別訪問 localhost:5000  localhost:3000 。

依次點選兩個頁面的按鈕,看看效果。

Svelte

React

從執行效果來看,Svelte 和 React 似乎在樣式上有點不同,但是功能已經完成了。你對這兩個框架的感覺怎樣呢?

結論

這是一次對 Svelte 有趣的探索,到目前位置二者能力差不多。Svelte 的模板語言非常有趣,特別是 on: 指令。實話實說我很懷念編寫 HTML 模板的日子。我一定會用 Svelte 來編寫更多的應用,同時我也將深入瞭解諸如生命週期和資料繫結方面的能力,這些對 React 當前階段來說還是有點痛苦的。

如果你也在學習 Svelte 的話,別忘了跟大家分享。

本文翻譯自 React vs. Svelte: Comparing the Basics (twilio.com)