SODA Engineering Blog
📑

React, Vue.js, Svelte比較してみよう

2024/11/28に公開

こんにちは。FEチームのMapleです。私たちのチームは、現在のシステムアーキテクチャを見直し、Reactを用いた新しいアーキテクチャへの移行を検討しています。直近ではもう少し視野を広げてVueからの移行にはSvelteのほうが向いているのではないかと考えているので振り返ってすべて比較してみます!

比較内容

以下の機能を持つシンプルなアプリケーションを実装します。

  1. カウンター機能:ボタンをクリックするとカウントが1増える。
  2. APIデータ取得:外部APIからデータを取得し、リスト表示する。
  3. フォームハンドリング:入力フォームからデータを取得し、リストに追加する。
  4. コンポーネント間通信:親子コンポーネント間でデータをやり取りする。

1. カウンター機能の実装

React

// Counter.jsx
import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

export default Counter;

Vue.js

<!-- Counter.vue -->
<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

<script>
export default {
  data() {
    return { count: 0 };
  },
  methods: {
    increment() {
      this.count++;
    }
  }
};
</script>

Svelte

<!-- Counter.svelte -->
<script>
  let count = 0;
</script>

<div>
  <p>Count: {count}</p>
  <button on:click={() => count++}>Increment</button>
</div>

2. APIデータ取得の実装

React

// DataFetcher.jsx
import { useState, useEffect } from "react";

function DataFetcher() {
  const [items, setItems] = useState([]);

  useEffect(() => {
    fetch("https://api.example.com/items")
      .then(response => response.json())
      .then(data => setItems(data));
  }, []);

  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

export default DataFetcher;

Vue.js

<!-- DataFetcher.vue -->
<template>
  <ul>
    <li v-for="item in items" :key="item.id">{{ item.name }}</li>
  </ul>
</template>

<script>
export default {
  data() {
    return { items: [] };
  },
  created() {
    fetch("https://api.example.com/items")
      .then(response => response.json())
      .then(data => (this.items = data));
  }
};
</script>

Svelte

<!-- DataFetcher.svelte -->
<script>
  let items = [];

  onMount(async () => {
    const response = await fetch("https://api.example.com/items");
    items = await response.json();
  });
</script>

<ul>
  {#each items as item}
    <li>{item.name}</li>
  {/each}
</ul>

3. フォームハンドリングの実装

React

// FormHandler.jsx
import { useState } from "react";

function FormHandler() {
  const [inputValue, setInputValue] = useState("");
  const [list, setList] = useState([]);

  const handleSubmit = e => {
    e.preventDefault();
    setList([...list, inputValue]);
    setInputValue("");
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input
          value={inputValue}
          onChange={e => setInputValue(e.target.value)}
          type="text"
        />
        <button type="submit">Add</button>
      </form>
      <ul>
        {list.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

export default FormHandler;

Vue.js

<!-- FormHandler.vue -->
<template>
  <div>
    <form @submit.prevent="handleSubmit">
      <input v-model="inputValue" type="text" />
      <button type="submit">Add</button>
    </form>
    <ul>
      <li v-for="(item, index) in list" :key="index">{{ item }}</li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      inputValue: "",
      list: []
    };
  },
  methods: {
    handleSubmit() {
      this.list.push(this.inputValue);
      this.inputValue = "";
    }
  }
};
</script>

Svelte

<!-- FormHandler.svelte -->
<script>
  let inputValue = "";
  let list = [];

  const handleSubmit = (e) => {
    e.preventDefault();
    list = [...list, inputValue];
    inputValue = "";
  };
</script>

<div>
  <form on:submit|preventDefault={handleSubmit}>
    <input bind:value={inputValue} type="text" />
    <button type="submit">Add</button>
  </form>
  <ul>
    {#each list as item}
      <li>{item}</li>
    {/each}
  </ul>
</div>

4. コンポーネント間通信の実装(親子コンポーネント)

React

// Parent.jsx
import Child from "./Child";

function Parent() {
  const handleChildData = (data) => {
    console.log("Received from child:", data);
  };

  return <Child onSendData={handleChildData} />;
}

export default Parent;

// Child.jsx
function Child({ onSendData }) {
  const data = "Hello from Child";
  return (
    <button onClick={() => onSendData(data)}>Send Data to Parent</button>
  );
}

export default Child;

Vue.js

<!-- Parent.vue -->
<template>
  <Child @send-data="handleChildData" />
</template>

<script>
import Child from "./Child.vue";

export default {
  components: { Child },
  methods: {
    handleChildData(data) {
      console.log("Received from child:", data);
    }
  }
};
</script>

<!-- Child.vue -->
<template>
  <button @click="sendData">Send Data to Parent</button>
</template>

<script>
export default {
  methods: {
    sendData() {
      this.$emit("send-data", "Hello from Child");
    }
  }
};
</script>

Svelte

<!-- Parent.svelte -->
<script>
  import Child from "./Child.svelte";

  function handleChildData(event) {
    console.log("Received from child:", event.detail);
  }
</script>

<Child on:sendData={handleChildData} />

<!-- Child.svelte -->
<script>
  import { createEventDispatcher } from "svelte";
  const dispatch = createEventDispatcher();

  function sendData() {
    dispatch("sendData", "Hello from Child");
  }
</script>

<button on:click={sendData}>Send Data to Parent</button>

コードの比較

特徴 React Vue.js Svelte
状態管理 useState, useEffectフック data, computed, methods 単純な変数とリアクティブ宣言
イベント処理 JSXでイベントを設定 ディレクティブ(@click)を使用 on:clickで簡潔に記述
フォームバインディング 明示的にvalueonChangeを設定 v-modelで双方向バインディング bind:valueで双方向バインディング
コンポーネント間通信 Propsとコールバック関数を使用 Propsと$emitでイベントを伝播 PropsとcreateEventDispatcherを使用
ライフサイクルフック useEffectフックで管理 created, mountedなどを使用 onMount, beforeUpdateなどを使用
テンプレート構文 JSX(JavaScript拡張) 独自のテンプレート構文 HTMLに近いテンプレート構文
学習コスト 高い 低め 低め
ランタイム依存 必要 必要 不要
パフォーマンス 仮想DOMを使用 仮想DOMを使用 直接DOM操作で高速

詳細な比較ポイント

1. 状態管理

  • ReactuseStateuseEffectフックを組み合わせて状態と副作用を管理。
  • Vue.jsdataで状態を宣言し、computedmethodsでロジックを定義。
  • Svelte:単純に変数を宣言し、リアクティブな更新は自動で処理。

2. イベントハンドリング

  • React:JSX内でイベントを直接設定し、関数を渡す。
  • Vue.js@clickなどのディレクティブを使用して直感的に設定。
  • Svelteon:eventの形式でシンプルに記述。

3. フォームバインディング

  • ReactvalueonChangeを手動で設定。
  • Vue.jsv-modelで双方向バインディングが簡単に実現。
  • Sveltebind:valueで双方向バインディングが可能。

4. コンポーネント間通信

  • React:親から子へはProps、子から親へはコールバック関数を渡す。
  • Vue.jsprops$emitを使ってデータの受け渡し。
  • SveltepropscreateEventDispatcherを使用してイベントを発行。

5. ライフサイクルフック

  • ReactuseEffectでライフサイクルに相当する処理を行う。
  • Vue.jscreatedmountedなどの明示的なフックを使用。
  • SvelteonMount, beforeUpdate, afterUpdateなどの関数を使用。

結論

  • 個人的にはSvelteがコードが完結かつVueからの移行なら良いのではないかと考え始めています!
  • 最初はTypeScriptとSvelteは相性悪いと持っていたのですが、そうでもないみたいですね。

参考資料

SODA Engineering Blog
SODA Engineering Blog

Discussion