SvelteとReactの基本を比較

February 23, 2021
執筆者
レビュー担当者
Diane Phan
Twilion

Scelte vs React

この記事はTwilio Developer VoicesチームのAshley Boucherが執筆したこちらの記事(英語)を日本語化したものです。

JavaScriptフレームワークについて語る時、Svelteは新顔です。これまでよく噂を耳にしてきましたが、今回試用し、Reactと比較します。

この記事では、SvelteとReactの違いについて検討し両方のフレームワークを用いて基本的なアプリを構築する方法を紹介します。目標は、以下を取り扱う、中心的概念をデモンストレーションすることです。

  • 構造化コンポーネント
  • 初期化ステート
  • propsの受け渡し
  • ステートのリフト
  • イベントリスナー
  • 動的なスタイル

さらに、条件付きレンダリングライフサイクルメソッドなど、他にも話すべきトピックは多数ありますが、この記事では基本に焦点を置き、その中で私の感想も述べていきます。

前提条件

このチュートリアルを開始するに当たり、以下のツールと技術が必要です。

Svelte対React

SvelteとReact.jsは、Webアプリケーション開発向けかつ、コンポーネントベースのJavaScriptフレームワークです。この2つの間の主な違いは、Svelteは仮想DOMを使用しないことです。Svelteはビルド時にコードをプレーンなJavaScriptにコンパイルするのに対し、Reactは実行時にコードを解釈します。

以下は、Svelteドキュメントから引用したものです。

Svelteは、ユーザーインターフェイスを構築するための急進的な新しいアプローチです。ReactやVueのような従来のフレームワークは、ほとんどの作業をブラウザで行いますが、Svelteは、その作業を皆さんがアプリを構築するときのコンパイルステップに移動します。

仮想DOM比較などのテクニックを使用する代わりに、Svelteはアプリの状態が変化すると、DOMを更新するコードを記述します。

いいですね。しかし、この調査においては、背景の詳細は重要ではありません。私は、これを開発者体験の観点から検討します。SvelteとReactでの開発とは、どのようなものでしょうか。使用していて気持ちの良いものでしょうか。楽しいでしょうか。直観的でしょうか。

見てみましょう。

アプリの準備

この比較チュートリアルでは、以下の要件を持つ、小さなアプリを構築します。

  • 3つのコンポーネント: AppHeadingButton
  • ボタンがクリックされるたびに、見出しが更新され、現在のクリック数を表示
  • ボタンがクリックされるたびに、ボタンの色が変化

以下のコマンドを使用し、svelte-reactという新しいディレクトリをコンピューターに作成します。

mkdir svelte-react
cd svelte-react

次のステップは、SvelteアプリとReactアプリを準備し、ローカルで実行することです。Svelteの設定プロセスは、Reactより1ステップ多くなっています。さらに、アプリが実行されるポートは、SvelteはPORT=5000ですが、ReactはPORT=3000です。

Svelte

ターミナルまたはコマンドプロンプトウィンドウで、以下のコマンドを実行します。

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

React

2つ目のターミナルを開き、svelte-reactディレクトリに戻ります。ここから、以下のコマンドを実行します。

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

Svelteコマンドの実行は先ほどのReactに比べて短時間で終了したと感じるでしょう。これはSvelteでは実際にユーティリティを実行するのではなくテンプレートを複製しているためです。

アプリコンポーネントの構築

上記のコマンドを実行した後は、SvelteとReact両方のスターターアプリに、すでに多くのファイルとコードがあることが分かります。

SvelteとReact両方のプロジェクトのコンポーネントファイルは、/srcフォルダ内にあります。表示された/srcフォルダの中を見ると、Svelteファイルは拡張子.svelteで終わることが分かります。Reactコンポーネントファイルは.jsで終わります。

Appコンポーネントがある各スターターアプリは、svelte-react/svelte-test/src/App.sveltesvelte-react/react-test/src/App.jsにあります。好みのコードエディタでこれらのファイルを開き、両方の中にあるコードを削除します。この後の記事で新しくコードを実装します。

コンポーネント構造

Svelte

Reactコンポーネントとは異なり、Svelteコンポーネントでは、昔のHTML、CSS、JavaScriptのような形式でコードを記述できます。

コンポーネントのすべてのJavaScriptは、ファイルの一番上にある<script></script>タグの内側に収めます。

<script></script>タグの下には、コンポーネントのHTMLを記述できます。そう、通常のHTMLドキュメントと同様です。そうなんです。

次に、HTMLの下で、<style></style>タグの内側にスタイルを追加します。各コンポーネントのスタイルは、そのコンポーネントのみに適用されます。つまり、例えば、それぞれのコンポーネントで<p>タグのスタイルを設定でき、インポート時にスタイルが互いにオーバーライドされません。

App.svelteの手始めにファイル内のすべてを削除し、空の<script>タグをいくつか追加します。

<script>
</script>

コンポーネントのコードの大半は、これらの<script>タグの内側に挿入されます。

React

ReactアプリでApp.jsファイルを開き、こちらもコンテンツを削除し以下を追加します。

function App() {

}

export default App;

このコードは、App()と呼ばれる基本機能コンポーネントを作成します。それをエクスポートします。ここでのSvelteとReactのもう1つの主な違いは、Svelteではコンポーネントをエクスポートしないことです。

インポート

前述のように、このアプリには、AppHeadingButtonの3つのコンポーネントがあります。SvelteアプリとReactアプリの両方で、HeadingコンポーネントとButtonコンポーネントは、Appにインポートされるため、それらをAppコンポーネント内の子コンポーネントとして使用できます。後でこれらのコンポーネントを記述しますが、ここでは、Appコンポーネントを構築する際に、それらを参照することを覚えておいてください。

Svelte

Svelteで、インポートを<script>タグの内側に追加します。ハイライトされた行を反映させまするよう、App.svelteファイルを編集します。

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

React

Reactへのインポートは、ファイルの一番上、関数(またはクラス)コンポーネントの間に配置されます。App.jsの一番上、App()関数の前に、以下のコードを追加します。

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

Reactの場合には、useStateフックもインポートします。これは、Appがステートフルなコンポーネントであるためです。Svelteではこのフックのアナログ値はありません。何もインポートする必要はありません。

stateの初期化

Appは、ステートフルなコンポーネントです。これは、colorcountの2つのステート値を持ちます。

colorはボタンの色です。この値は、propsとしてButtonコンポーネントに渡され、Buttonコンポーネントがクリックされるたびに変化します。初期化される際の値は#000000です。これは、黒色を示す16進数コードです。

countButtonがクリックされた回数を表します。初期化される際の値は0です。

Svelte

Svelteでは、変数を割り当てることによりステートが作成されます。インポートの下で、<script>タグの間に、以下のステート宣言を追加します。

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

さらにSvelteでは、リアクティブ宣言が提供されており、ステート値を再計算することができます。これを使用する必要はありませんが、変化する他のステート値から派生するステート値を使用する場合に非常に便利です。

ここで覚えておくべきは、SvelteでのDOMの更新はstate変数の割り当てによりトリガされることです。ステートにアレイやオブジェクトが含まれる場合には、.push()のようなメソッドで更新しても、DOMの更新はトリガされません。Svelteでのこの更新の管理方法の詳細については、配列とオブジェクトの更新に関するドキュメントをご覧ください。

React

useStateフックはすでにインポートしました。今度はそれを実際に使用します。

App()関数内の、App.js内に、以下のステート宣言を追加します。

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

このコードでcountと呼ばれるstate変数が、初期値0と、その値を更新するメソッドsetCount()と共に作成されます。同様に、colorと呼ばれるstate変数も、初期値#000000と、その値を更新するメソッドsetColor()と共に作成されます。この時点で、Svelteのステート初期化は、読み込みも使用も非常に簡単です。

コンポーネントをレンダリングし、propsを渡す

いずれのプロジェクトでも、目標は、<main>エレメントから構成されるユーザーインターフェイスを作成することです。そのエレメントが2つのネストされたコンポーネントHeadingButtonをラップします。

Appコンポーネントは、これらの子コンポーネントの両方にpropsを渡します。handleClick()というハンドラー関数に加え(この関数はこの後で記述します)、Headingコンポーネントはcountステート値を受け取り、Buttonコンポーネントはcolorステート値を受け取ります。

Svelte

Svelteは独自のテンプレート作成言語を使用することにより、ReactはJSXを使用することにより、ユーザーインターフェイスを作成します。Svelteのテンプレート作成言語は、Flask、PHP、Liquidのように、サーバー側環境でのHTMLテンプレート記述に非常に似ています。この記事では詳細を取り扱いませんが、素晴らしいのは、直接ファイル内で<script>タグの下にHTMLを記述できることです。

ハイライトされているコードをコピーし、App.svelteファイルの、<script>タグの下に貼り付けてください。

<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()関数から、ユーザーインターフェイスのJSXを返します。

これが、SvelteとReactが最も類似している部分です。propsの渡し方が完全に同じです。この例では、Svelteのテンプレート作成は、ReactのJSXと完全に同じに見えます。

Svelteに興味を持つReact開発者であれば、propsの渡し方について驚くことや目新しいことはありません。一方、propsの受け取り方については、Svelteは少し異なります。これについては、この記事で後述します。

ステートのリフト

このアプリを機能させるには、Buttonがクリックされるたびに、Appコンポーネントのcountステート値が増加する必要があります。つまり、子コンポーネントから親コンポーネントにデータをリフトアップするメカニズムが必要です。

皆さんは、handleClick()関数をButtonコンポーネントにpropsとして渡すことにより、このプロセスをすでに開始しています。

これから記述するこの関数は、Appコンポーネントに属します。この関数をpropsとして子Buttonコンポーネントに渡すと、Buttonコンポーネントはこの関数にアクセスでき、ボタンがクリックされるたびに呼び出すことができます。この方法により、Appコンポーネントは子コンポーネントで何が起きているかを把握できます。

この関数は、Appコンポーネントのcountcolorのステート値を更新します。

Svelte

以下のコードをコピーし、App.svelte<script>タグ内側のステート宣言後に貼り付けます。

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

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

React

以下のコードをコピーし、App.jsの、App()関数内のreturnステートメントのすぐ上に貼り付けます。

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

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

Reactでは、ステート値を更新するために、前に宣言したsetCount()メソッドとsetColor()メソッドを使用する必要がありますが、Svelteでは直接更新できます。

これでAppコンポーネントでの作業は終わりです。次に、Headingコンポーネントに移ります。

Headingコンポーネントの構築

Headingコンポーネントは、アプリの見出しテキストとクリックカウンターを表示します。これはステートフルなコンポーネントではなく、ボタンが何回クリックされたかを示す、1つのデータ(count)をpropsとして受け取ります。

Svelteプロジェクトのsrcフォルダ内に、Heading.svelteという名前の新しいファイルを作成します。

Reactプロジェクトのsrcフォルダ内に、Heading.jsという名前の新しいファイルを作成します。

propsを受け取る

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>タグ内側のハイライトされた行を見てください。この行は、コンポーネントがcountというpropを受け取ることを、Svelteに示します。

この結果、上記の6行目に見られるように、countHeadingコンポーネントのHTMLテンプレート内で直接使用できます。

この方法での記述は少し通常と異なりますが、ファイルの一番上で直ちにpropsを宣言すると、見た目が整理され、すぐにpropを使用でき、気持ちがよいものです。

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 }パラメータを1つ持ち、このパラメータはcount値であり、コンポーネントに渡されたpropsオブジェクトから非構造化されたものです。

Buttonコンポーネントの構築

Buttonコンポーネントは、UIで<button>エレメントをレンダリングします。このコンポーネントは、color<button>エレメントのスタイルに使用される)とhandleClick()<button>エレメントがクリックされると呼び出される関数)の2つのpropsを受け取ります。

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>

このコードでは、両方のpropsは、ファイルの一番上にある<script>タグ内で宣言されます。

次に、<button>エレメントが作成されます。6行目に新しい構文があることに気付くと思います。現時点ではこのスタイル部分は無視してください。後で説明します。ここでは、クリックイベントのリスナーの構文に注目してください。従来のものとは異なる構文です。

Svelteは、on:ディレクティブを使用することにより、DOMエレメントにイベントリスナーを追加します。さらにSvelteには、便利なイベント修飾子があります。これを使用すると、例えば、最初のクリックでクリックイベントを1つトリガするだけにすることが可能になります。また、独自のコンポーネントイベントをディスパッチすることもできます。

React

以下のコードをButton.js内に追加します。

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

export default Button;

サーバーがまだ実行中で、この時点でエラーが発生する場合には、心配しないでください。stylesオブジェクトを追加すると、エラーが修正されます。

上記のコードにより、Button()という新しい機能コンポーネントが作成されます。これは1つの引数(props)を受け取り、propsはcolorhandleClickに分解されます。handleClick()関数はhandleClick props上で利用でき、標準のインラインonClickイベントを<button>エレメントJSXで使用できます。

動的なスタイル

このアプリでは、Buttonコンポーネントは、カラーをpropsとして受け取ります。このカラー値は、<button>エレメントの背景色になります。

Svelte

動的なスタイルはもちろんSvelteで実現可能ですが、想像したほどシンプルではありませんでした。

Svelteでは残念ながら、<style>タグ内で直接propsの値を使用することはできません。ただし、コンポーネントのHTMLを、JavaScriptとCSSの間でコミュニケーションする方法として使用できます。

以下のスタイルをコピーし、Button.jsで、ファイルの最後のButtonコンポーネントのHTMLの下に貼り付けます。

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

background-colorスタイルプロパティは、color propsを直接参照していませんが、colorというスタイル変数を参照しています。これは、前のセクションにおいて、HTMLで<button>エレメントに作成したものです。

これは少し手間がかかりますが、その代わりに、ローカルでスコープされる実際のCSSを記述できます。(心配しないでください。グローバルCSSも使用できます)。

React

Reactでは、何種類もの方法でコンポーネントにスタイルを追加でき、動的スタイルには特にインラインスタイルが最も一般的に使用されます。

JSXでインラインスタイルを使用するには、スタイルでオブジェクトを作成し、次にそのオブジェクトをエレメントのstyle属性に割り当てます。オブジェクトの割り当ては前のセクションで行いました。

以下のコードをコピーし、Button()関数内の、returnステートメントの前に貼り付け、stylesオブジェクトを作成します。

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

アプリをテスト

すべてのファイルを保存し、閉じます。プロジェクトサーバーが実行されていない場合、ここで再起動します(Svelteではnpm run dev、Reactではnpm start)。次にブラウザタブの1つでlocalhost:5000にアクセスし、次のブラウザタブでlocalhost:3000にアクセスします。

各アプリが実行されたら、ボタンをクリックし、見出しにカウントの更新が表示されボタンの色が変化するかどうかを確認します。

Svelte

Svelte app

React

React app

SvelteとReactは、マージン、パディング、ボタンのネイティブスタイルにいくつかの違いがありますが、全体としては、2つの異なるフレームワークに、2つの同じ機能を持つアプリを構築できます。どう思われましたか?作業はどうでしたか?

まとめ

このSvelteの調査は、非常に興味深いものでした。これまでのところ、SvelteとReactの機能は類似しているようです。Svelteのテンプレート作成言語は、特にon:ディレクティブが非常に魅力的です。正直なところ、テンプレート化されたHTML記述が懐かしいです。今後もSvelteで構築を続け、Reactでは困難な、ライフサイクルメソッドやデータバインディングのような高度な概念をさらに検討していきたいと思います。

Svelteでの作業のご感想を、Twitterでお聞かせください!

Ashleyは、TwilioブログのJavaScriptエディターです。Ashleyと協力し、Twilioにテクニカルストーリーを紹介するには、Twitterで@ahl389までご連絡ください。TwitterでAshleyが見つからない場合は、どこかのパティオでコーヒーを飲んでいることでしょう(ワインの時間かも)。