ReactとSvelteの違い：主要機能を比較


[![Group185.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/4e4e144d-1797-46c0-b5de-eaee61417606.png)](https://leapcell.io/)


**[Leapcell: The Best of Serverless Web Hosting](https://leapcell.io/)**

# ReactとSvelteフレームワークの比較：レンダリングモードから機能実装まで

Reactと同じように、Svelteはフロントエンドインターフェイスを構築するために使用される反応型UIフレームワークで、開発者がコンポーネントベースの方法でページコードを整理できるようにします。最近、YouTubeでReactとSvelteフレームワークの違いを10の実践例を通じて比較する動画を見ました。その内容は非常に面白く、学習にとても価値があります。そのため、この記事では動画の内容を整理し、私自身の理解を加えて皆さんに参考になるようにし、この2つのフレームワークについてより深く理解するお手伝いをします。

| 比較項目 | React | Svelte |
|--|--|--|
| レンダリングモード | 仮想DOM（Virtual DOM）を使って、ページの変更が必要な部分を計算します。組み込みのランタイムが必要で、コードサイズが比較的大きいです（例えば、Next.jsでのHello WorldアプリケーションはJavaScriptコードが約70kbあります）。 | ビルドプロセス中にコードをコンパイルします。ランタイムではなくコンパイラを使用し、最終的な生成物にはSvelteライブラリコードは含まれません。コードサイズが小さいです（例えば、Hello Worldアプリケーションはたった3kbです）。 |
| 状態（state） | `useState`を使って反応型の状態とセッター関数を生成します。セッターを呼び出すとUIが再レンダリングされます。 | `let`で宣言された変数は反応型です。変数の値が変更されると、フレームワークが自動的にUIを更新します。 |
| プロパティ（props） | 関数型コンポーネントでは、プロパティを関数の引数として受け取り、通常は破棄代入を使用してプロパティの値を取得します。プロパティにはコンポーネントも指定できます。 | 変数を宣言するときに`export`キーワードを付けると、それが外部から渡されるプロパティであることを示します。`{プロパティ名}`という構文シュガーを提供してプロパティを渡しますが、プロパティにコンポーネントを指定することはできません。 |
| 子コンポーネント（children） | `props.children`を通じて子コンポーネントの情報を取得します。 | スロット`slot`を通じて実装され、デフォルトスロットと名前付きスロットをサポートしています。 |
| ライフサイクル | 関数型コンポーネントでは、`useEffect`を通じてライフサイクルをシミュレートします（例えば、`useEffect`内で関数を返すことでコンポーネントのアンマウント処理を行います）。 | `script`内で`onMount`や`onDestroy`などのライフサイクル関数をインポートします。 |
| 副作用（Side Effects） | `useEffect`を通じて副作用を宣言し、副作用が依存する変数は手動で宣言する必要があります。 | `$`記号で始まる反応型式で副作用を宣言し、依存する変数を明示的に宣言する必要はありません。 |
| 計算プロパティ（Computed Properties） | `useMemo`を使って計算プロパティを作成します。最初の引数は計算プロパティの値を返す関数で、2番目の引数は依存配列です。 | `$`式を使って計算プロパティを作成し、依存する変数が変更されたときに自動的に更新されます。 |
| 条件付きレンダリング（Conditional Rendering） | JavaScriptの三項演算子を使って条件付きレンダリングのロジックを表現します。 | 伝統的なテンプレート言語に似た構文（`{#if} {:else if} {:else} {/if}`）を採用しており、複雑なロジックにおいてはより明瞭です。 |
| ループ（Looping） | `map`を使って配列をトラバースし、コンポーネントを返すことでループレンダリングを実現し、`key`を設定する必要があります。 | `each`を通じてループレンダリングを行い、`(変数.id)`はレンダリングの`key`を表します。 |
| グローバル状態管理（Global State Management） | `createContext`を使って`Context`を作成し、ルートコンポーネントで`Provider`で子コンポーネントをラップし、子コンポーネントで`useContext`を使って状態を取得します。 | `writable`を使ってグローバルストアを宣言します。コンポーネント内で`$+変数名`を使って読み取り、`store.update()`を使って更新します。構文がより簡潔です。 |
| 非同期レンダリング（Asynchronous Rendering） | React18では`use`フックが導入され、非同期コードを実行できるようになりました。非同期コンポーネントは`Suspense`でラップされ、`ErrorBoundary`と一緒に使用してロードとエラーを処理できます。 | JavaScriptに似たテンプレート構文（`{#await} {:then} {:catch} {/await}`）を提供して、非同期レンダリングとエラーキャッチを処理します。 |

## 0. レンダリングモード
ReactとSvelteの両方が反応型UIフレームワークですが、それぞれのレンダリングモードはまったく異なります。
Reactは仮想DOMを使ってページの変更が必要な部分を計算します。これはすべてのReactアプリケーションに組み込みのランタイムが必要であることを意味しており、つまり、仮想DOMを計算してページをレンダリングするためのコードが含まれています。これにより、コードサイズが増加します。例えば、Next.jsで作成されたHello Worldアプリケーションには70kbのJavaScriptコードがあります。

Svelteはまったく異なる戦略を採用しています。アプリケーションのビルド段階で、Svelteは開発者が書いたコードをコンパイルし、ランタイムではなくコンパイラを使用します。最終的に生成される生成物にはSvelteライブラリコードは含まれません。そのため、SvelteのHello Worldアプリケーションはたった3kbです。

Svelteは非JSコードをJSコードにコンパイルする一方で、Reactアプリケーションのコードは純粋なJSコードですが、驚くべきことに、SvelteはネイティブJavaScriptのサードパーティライブラリとより良く連携することができます。ただし、Reactはより成熟したエコシステムを持っています。

## 1. 状態（state）
まず、2つのフレームワークで最も単純な状態管理を実現する方法を比較します。
Reactでは、`useState`を使って反応型の状態`count`とそれに対応するセッター関数`setCount()`を生成する必要があります。`setCount()`を呼び出して`count`の値を更新すると、UIが再レンダリングされます。
```jsx
import { useState } from "react";

function Counter() {
  // useStateを使って状態countを0で初期化し、更新関数setCountを取得
  const [count, setCount] = useState(0);
  return (
    <div>
      {/* ボタンをクリックするとsetCountを呼び出してcountの値を増やし、現在のcountの値を表示 */}
      <button onClick={() => setCount(count + 1)}>Count is {count}</button>
    </div>
  );
}
```
Svelteでは、`let`キーワードで変数を宣言するだけで、それが反応型になります。Svelteコンポーネントのコード内で反応型変数`count`を宣言します。Svelteコンポーネントのコードは`script`、`style`、`template`の3つの部分に分かれています。違いは、SvelteではHTMLを`template`タグで囲む必要がないことです。`count`の値を変更するには、通常の変数と同じように操作すればよく、フレームワークが自動的に反応型のUI更新を行います。
```html
<script>
  // 反応型変数countを宣言し、0で初期化
  let count = 0;
</script>

{#if true}
  <!-- ボタンをクリックするとcountの値を増やし、現在のcountの値を表示 -->
  <button on:click={() => count++}>
    count is {count}
  </button>
{/if}
```

## 2. プロパティ（props）
次に、2つのフレームワークでプロパティを受け取り、渡す方法を見てみましょう。
Reactの関数型コンポーネントでは、プロパティを関数の引数として受け取り、通常は破棄代入の方法を使ってプロパティの具体的な値を取得します。
```jsx
function ColoredBox({color}) {
  // 破棄代入を使ってcolorプロパティの値を取得し、表示
  return (
    <p>You picked: {color}</p>
  )
}
```
Svelteでは、変数を宣言するときにその前に`export`キーワードを付けると、その変数が外部から渡されるプロパティであることを意味します。
```html
<script>
  // colorを外部から渡されるプロパティとして宣言
  export let color; 
</script>

{#if color}
  You picked: {color}
{/if}
```
変数を渡す点では、2つの構文は似ており、どちらもHTML属性の形式です：
```html
<App color={color} />
```
Svelteはさらに構文シュガーを提供して、プロパティの渡し方をより簡潔にします：
```html
<App {color} />
```
注意すべきは、Reactのプロパティにはコンポーネントを指定できる一方で、Svelteはこの方法をサポートしていないということです。
```jsx
<App head={<Head />} />
```

## 3. 子コンポーネント（children）
Reactでは、`props.children`を通じて子コンポーネントの情報を取得することができます。
```jsx
function ParentComponent(props) {
  // props.childrenを使って子コンポーネントの内容を取得し、表示
  return (
    <div>
      {props.children}
    </div>
  );
}
```
Svelteでは、この機能はスロット`slot`を通じて実装する必要があります。
```html
<!-- Widget.svelte -->
<div>
  <slot>
    <!-- 子コンポーネントの内容がない場合、この内容がデフォルトで表示されます -->
    If there is no content of child components, this content will be displayed by default.
  </slot>
</div>

<!-- App.svelte -->
<Widget />
<!-- ⬆️このコンポーネントはデフォルトの内容を表示します -->

<Widget>
  <p>This child component will overwrite the default content</p>
</Widget>
```
Svelteは名前付きスロットもサポートしています：
```html
<!-- Widget.svelte -->
<div>
  <slot name="header" />
  <p>Content between the header and the footer</p>
  <slot name="footer" />
</div>

<!-- App.svelte -->
<Widget>
  <h1 slot="header">Hello</h1>
  <p slot="footer">Svelte Industries</p>
</Widget>
```

## 4. ライフサイクル
Reactの関数型コンポーネントでは、`useEffect`を通じてライフサイクルをシミュレートする必要があります。
```jsx
useEffect(() => {
  // コンポーネントが初期化されたとき実行され、onMountに相当
  console.log('Component initialized');
  return () => {
    // コンポーネントがアンマウントされたときに実行され、onDestroyに相当
    console.log('Component unmounted');
  }
}, [])
```
Svelteでは、`script`内で対応するライフサイクル関数をインポートするだけです。
```html
<script>
  import { onMount, onDestroy } from 'svelte';

  onMount(() => {
    console.log('Component mounted');
  });

  onDestroy(() => {
    console.log('Component unmounted');
  });
</script>
```

## 5. 副作用（Side Effects）
Reactでは、`useEffect`を通じて副作用を宣言し、副作用が依存する変数は`useEffect`の第二引数を使って手動で宣言します。
```jsx
function Counter() {
  const [count] = useState(0);

  useEffect(() => {
    // countが変更されたとき、document.titleを更新
    document.title = `count is ${count}`;
  }, [count])
}
```
Svelteでは、`$`記号で始まる反応型式を通じて副作用を宣言することができます。
```html
<script>
  let count = 0;

  // countが変更されたとき、自動的にdocument.titleを更新
  $: document.title = `count is ${count}`;
</script>
```
`$:`の後の文は自動的に反応型の機能を持ちます。文内で参照される変数が変更されたとき、その文は自動的に実行され、これは変数の変更による副作用に相当します。比較すると、Svelteは副作用の文が依存する変数を明示的に宣言する必要がなく、Reactよりも使い勝手が良いです。

## 6. 計算プロパティ（Computed Properties）
計算プロパティとは、その値が`state`に依存する変数で、Vueの`computed`に似たものです。Reactでは、`useMemo`を通じて計算プロパティを作成することができます。`useMemo`の第一引数は関数で、その返り値が計算プロパティの値となります。第二引数は依存配列で、この依存配列内の変数が変更されたとき、計算プロパティの値が再計算されます。
```js
function Counter() {
  const [count] = useState(0);

  // useMemoを使って計算プロパティdoubleを作成し、countに依存
  const double = useMemo(() => count * 2, [count]);
}
```
Svelteでは、前節で述べた`$`式を使って計算プロパティを作成することもできます。
```html
<script>
  let count = 0;

  // 計算プロパティdoubledを作成し、countに依存。countが変更されたとき、doubledが再代入される
  $: doubled = count * 2
</script>
```

## 7. 条件付きレンダリング（Conditional Rendering）
ReactはJSXを使ってUIを記述するため、JavaScriptの三項演算子を使って条件付きレンダリングのロジックを実装することができます。
```jsx
function Counter() {
  const [count] = useState(0);

  return <>
    {/* countの値に応じて条件付きレンダリング */}
    { count > 1000 ? <p>Big</p> : <p>Small</p> }
  </>
}
```
Svelteは、伝統的なテンプレート言語に似た構文を使って条件付きレンダリングのロジックを表現します。
```html
<script>
let count = 0
</script>

{#if count > 1000}
  <p>Big</p>
{:else if count > 500}
  <p>Medium</p>
{:else}
  <p>Small</p>
{/if}
```
比較すると、Svelteの構文はやや煩雑ですが、`else if`文が存在するため、複雑な条件付きレンダリングのロジックを表現するときにはReactの三項演算子よりも明瞭です。

## 8. ループ（Looping）
Reactでは、`map`を使って配列をトラバースし、一連のコンポーネントを返すことでループレンダリングを実現することができます。
```jsx
function Looper() {
  const items = [
    { id: 1, name: "foo" },
    { id: 2, name: "bar" },
    { id: 3, name: "baz" }
  ];

  return <>
    {/* items配列をmapでトラバースし、コンポーネントをレンダリングしてkeyを設定 */}
    {items.map(item => <p key={item.id}>{item.name}</p>)}
  </>
}
```
Svelteでは、`each`を通じてループレンダリングを行うことができます。ここで`(item.id)`は、レンダリングの`key`が`item.id`であることを示しています。
```html
<script>
  const items = [
    { id: 1, name: "foo" },
    { id: 2, name: "bar" },
    { id: 3, name: "baz" }
  ];
</script>

{#each items as item (item.id)}
  <p>{item.name}</p>
{/each}
```

## 9. グローバル状態管理（Global State Management）
Reactでは、複数のコンポーネントで共有する状態を作成したい場合、`Context`を通じて実現することができます。まず、`createContext`を使って`CountContext`を作成し、そしてこの`Context`の`Provider`を使ってルートコンポーネント`App`内の子コンポーネントをラップします。こうすることで、子コンポーネント`Counter`内で`useContext`を使って`CountContext`内の内容を取得することができます。
```jsx
// context.js
import { createContext } from 'react';

// CountContextを作成し、0で初期化
export const CountContext = createContext(0);

// Counter.jsx
import { useContext } from 'react';
import { CountContext } from './context';

function Counter() {
  // useContextを使ってCountContext内の値を取得
  const count = useContext(CountContext);

  return <div>{count}</div>;
}

// App.jsx
import { CountContext } from './context';
import { Counter } from './Counter';

function App() {
  return <>
    {/* CountContext.Providerを使って子コンポーネントをラップし、状態を渡す */}
    <CountContext.Provider value={42}>
      <Counter />
    </CountContext.Provider>
  </>;
}
```
Svelteでは、`writable`を通じてグローバルストアを宣言することができます。グローバル状態を使用するコンポーネント内でストアをインポートし、`$+変数名`の方法でストアを読み取り、`store.update()`を呼び出してストアを更新します。
```js
// store.js
import { writable } from "svelte/store";

// グローバルストアcountを宣言し、0で初期化
export const count = writable(0);

// App.svelte
<script>
import { count } from "./store";
</script>

<button onClick={() => count.update(c => c + 1)}>
  {/* $countを使ってグローバル状態countの値を読み取る */}
  {$count}
</button>
```
個人的には、Svelteのグローバル状態管理の構文がより簡潔だと思います。`Provider`を書く必要がなく、`$`記号一つでグローバル状態を使用することができます。

## 10. 非同期レンダリング（Asynchronous Rendering）
React18では非同期レンダリングメカニズムが導入されました。新しい`use`フックを使って非同期コードを実行することができ、その効果は`await`文に似ています。`use`を使用するコンポーネントは非同期コンポーネントで、非同期コードの実行が完了するまでコンポーネントはレンダリングされません。
```jsx
function AsyncComponent() {
  const number = use(Promise.resolve(100));

  return <p>{number}</p>;
}
```
非同期コンポーネントを使用するときは、`Suspense`コンポーネントでラップすることができ、`fallback`コンポーネントを渡すことができます。`fallback`コンポーネントは、非同期コンポーネントがまだレンダリングされていないときに表示され、ロード状態を追加するために使用されます。また、非同期レンダリング中にエラーが発生するのを防ぐため、`ErrorBoundary`を使ってエラーをキャッチすることもでき、エラーが発生したときに対応する`fallback`コンポーネントが表示され、ページがクラッシュして空白になるのを避けます。
```jsx
function App() {
  return (
    <ErrorBoundary fallback={<ErrorPage />}>
      <Suspense fallback={<LoadingSpinner/>}>
        <ComponentWithAsyncData />
      </Suspense>
    </ErrorBoundary>
  )
}
```
Svelteでは、フレームワークがJavaScriptに似たテンプレート構文を提供して、非同期レンダリングとエラーキャッチのニーズを満たしています。
```html
<script>
  const promise = Promise.resolve(69);
</script>

{#await promise}
  <LoadingSpinner/>
{:then number}
  <p>The number is {number}</p>
{:catch error}
  <ErrorPage {error} />
{/await}
```

## まとめ
この記事では、ReactとSvelteフレームワークをレンダリングモード、状態管理、プロパティの渡し方、子コンポーネントの扱い、ライフサイクル、副作用、計算プロパティ、条件付きレンダリング、ループ、グローバル状態管理、非同期レンダリングの10の面から詳細に比較し、それぞれの基本的な使い方をカバーしました。この記事を読んだ後、読者の皆さんがSvelteについてより包括的な理解を得られたと信じています。この2つのUIフレームワークはそれぞれ独自の長所を持っています。あなたはどちらをより好きですか？コメント欄であなたの意見を共有することを歓迎します。 

## 参考文献:
* https://www.youtube.com/watch?v=MnpuK0MK4yo
* https://fireship.io/lessons/svelte-for-react-developers/

# [Leapcell: The Best of Serverless Web Hosting](https://leapcell.io/)

最後に、nodejsサービスをデプロイするのに最適なプラットフォームをおすすめします：**[Leapcell](https://leapcell.io/)**

[![brandpic7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/dc5032ff-88f0-4ab3-9e6e-b30b6e49a28b.png)](https://leapcell.io/)


### 🚀 好きな言語で構築
JavaScript、Python、Go、またはRustで簡単に開発できます。  

### 🌍 無料で無制限のプロジェクトをデプロイ
使用する分だけ支払います—リクエストがなければ、料金もかかりません。  

### ⚡ 使った分だけ支払い、隠された費用はありません
アイドル料金はなく、シームレスにスケーリングできます。  

[![Frame3-withpadding2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/055c16ab-3d7c-4a58-ba73-9954f8cfacd7.png)](https://leapcell.io/)


[📖 ドキュメントを参照する](https://docs.leapcell.io/)  

🔹 Twitterでフォローしてください： [@LeapcellHQ](https://x.com/LeapcellHQ)