Vue.js Composition APIの活用とOptions APIとの違いを解説

フロントエンド開発の現場では、アプリケーションの規模や複雑さが増す中で、より柔軟で保守しやすいアーキテクチャが求められています。Vue.jsは、これまでOptions APIを中心に使いやすさを追求してきましたが、Vue 3の登場とともに、新たな構築手法であるComposition APIが導入されました。本記事では、Composition APIの基礎からOptions APIとの違い、そして実際の開発における活用方法までを、段階的に詳しく解説していきます。


Vue.js Composition APIを深く理解する

📚 目次


1. はじめに:なぜComposition APIなのか?

JavaScriptフレームワークの世界は日々進化しており、Vue.jsもその例外ではありません。Vue 3のリリースに伴い導入されたComposition APIは、従来のOptions APIに代わるものというよりも、より柔軟で構造的なアプローチを可能にする新しい選択肢です。これは単なる構文の変更にとどまらず、UI構築における思考方法そのものに変革をもたらすものです。

Options APIは直感的で学習コストが低いため、多くの開発者にとって入り口としては非常に優れた手法です。しかし、コンポーネントが大規模になり、複数のロジックや状態を扱うようになると、コードが分散し、保守や再利用が困難になるという課題が現れます。

Composition APIはこの課題を解決するために、機能ごとにロジックをまとめ、setup()関数内に集約することで、より高い凝集性と再利用性を実現します。本記事では、この新しいAPIがなぜ必要とされ、どのように構築や運用に役立つのかを、実例を交えながら分かりやすく解説していきます。


2. Options APIとComposition APIの概要

Vue.jsを初めて学ぶ多くの開発者にとって、Options APIは親しみやすく、直感的に理解しやすい構文です。datamethodscomputedwatchといったオプションを明示的に定義することで、どのロジックがどこにあるのかが一目で分かるようになっています。この構成は、小規模なコンポーネントやシンプルなアプリケーションには非常に有効です。

しかし、アプリケーションの規模が大きくなり、複数のデータ状態や副作用のある処理を扱うようになると、Options APIの構造的な限界が浮き彫りになります。関連するロジックが別々のセクションに分断されることで、コードの可読性や保守性が低下し、ロジックの一貫性が損なわれるケースが多くなってきます。

こうした課題を解決するために登場したのが、Vue 3で導入されたComposition APIです。Composition APIでは、全てのロジックをsetup()関数内にまとめて定義でき、機能単位でコードを整理することが可能になります。つまり、状態管理、ロジック、イベント処理など、関連するコードを一つのまとまりとして構築できるのです。

このように、Options APIが「構成(オプション)ベース」でコードを記述するのに対し、Composition APIは「機能ベース」でのロジック設計を促します。これにより、より大規模な開発やチーム開発においても、コードの再利用性や保守性を大幅に向上させることができます。

Options APIとComposition APIの概要

次のセクションでは、実際のコード例を通じて、Options APIとComposition APIがどのように構造的に異なるのかを視覚的に比較しながら理解を深めていきましょう。


3. コード比較で見る構造の違い

Composition APIの特徴を理解するには、実際のコードを比較するのが最も効果的です。ここでは、簡単な「Todoリスト」コンポーネントを例に、同じ機能をOptions APIとComposition APIでそれぞれ実装し、その違いを明確にしていきます。

Options APIでの実装

export default {
  data() {
    return {
      todos: [],
      newTodo: ''
    };
  },
  methods: {
    addTodo() {
      if (this.newTodo.trim()) {
        this.todos.push(this.newTodo.trim());
        this.newTodo = '';
      }
    }
  }
};

この実装では、状態はdata()、ロジックはmethodsに分けて定義されています。小規模なアプリケーションでは直感的でわかりやすい構造ですが、ロジックが増えてくると関連する処理が分散し、可読性や保守性が低下する可能性があります。

Composition APIでの実装

import { ref } from 'vue';

export default {
  setup() {
    const todos = ref([]);
    const newTodo = ref('');

    const addTodo = () => {
      if (newTodo.value.trim()) {
        todos.value.push(newTodo.value.trim());
        newTodo.value = '';
      }
    };

    return {
      todos,
      newTodo,
      addTodo
    };
  }
};

Composition APIでは、状態(ref)とロジック(addTodo)を一箇所、すなわちsetup()関数の中で定義しています。これにより、機能単位でコードがまとまり、ロジックの再利用や保守がしやすくなります。

また、Composition APIではロジックを「コンポジション関数(Composition Function)」として外部に切り出すことも容易で、複数のコンポーネント間でロジックを共有する際に非常に有効です。

次のセクションでは、Composition APIで使用されるrefreactivecomputedwatchなどの基本機能を、実例と共に詳しく見ていきましょう。


4. Composition APIの核心概念

Composition APIの理解を深めるには、その基本構造と要素を正確に押さえることが重要です。このセクションでは、Vue 3で導入された主要な仕組みと、それぞれが果たす役割について、実用的な視点で解説していきます。

1)setup()関数

Composition APIにおけるすべての処理はsetup()関数から始まります。この関数はコンポーネントのインスタンスが作成される前に呼び出され、状態管理、イベントロジック、リアクティブデータの初期化など、ほぼすべてのロジックを記述できます。

export default {
  setup() {
    // ここにロジックを記述します
  }
};

2)refreactive

Vue 3では、リアクティブな状態を作成するためにref()reactive()が用意されています。それぞれの用途は以下の通りです。

  • refプリミティブ型(文字列、数値など)のリアクティブ変数を作成
  • reactiveオブジェクト全体をリアクティブに扱う
import { ref, reactive } from 'vue';

const count = ref(0);
const user = reactive({ name: '山田', age: 28 });

refで定義した変数にアクセスするには.valueを使う必要がありますが、reactiveは通常のオブジェクトと同じように扱えます。

3)computedwatch

computedは依存する状態に基づいて動的に値を計算し、キャッシュまで行う便利な機能です。一方watchは、ある状態が変化した際に副作用的な処理を実行するのに適しています。

import { ref, computed, watch } from 'vue';

const firstName = ref('太郎');
const lastName = ref('佐藤');

const fullName = computed(() => `${firstName.value} ${lastName.value}`);

watch(fullName, (newVal, oldVal) => {
  console.log(`名前が ${oldVal} から ${newVal} に変更されました`);
});

4)provideinject

親コンポーネントから深い子コンポーネントにデータを渡したい場合は、provideinjectが有効です。これにより、propsを何層にも渡すことなく、グローバルな状態や設定を共有できます。

import { provide, inject, ref } from 'vue';

// 親コンポーネントで
provide('theme', ref('dark'));

// 子コンポーネントで
const theme = inject('theme');

5)Composition Function(コンポジション関数)

複数のコンポーネントで共通して使いたいロジックは、コンポジション関数として切り出すことで、コードの再利用性と可読性が向上します。これはReactのカスタムフックと類似した概念です。

// useCounter.js
import { ref } from 'vue';

export function useCounter() {
  const count = ref(0);
  const increment = () => count.value++;
  return { count, increment };
}

このような関数をsetup()内で呼び出すことで、複雑なロジックもシンプルに、かつ再利用可能な形で管理できます。

次のセクションでは、Composition APIの具体的なメリットと、導入する上で注意すべきポイントについて詳しく見ていきましょう。


5. メリットと注意点

Composition APIは、Vue.jsを用いたアプリケーション開発において、柔軟性と再利用性の高い設計を可能にする強力なツールです。しかし、その一方で、すべてのケースにおいて万能というわけではなく、使用時にはいくつかの注意点も存在します。このセクションでは、実際の開発経験に基づいたメリットとリスクを客観的に整理していきます。

✔️ 主なメリット

  • 1. ロジックの凝集度が高くなる
    関連する状態や処理を機能単位でまとめられるため、コードの可読性と保守性が大幅に向上します。例えば、フォーム処理やAPI通信などをそれぞれ独立したロジックとして整理できます。
  • 2. 再利用性とテストのしやすさ
    Composition Functionとして切り出すことで、複数コンポーネント間でロジックを簡単に共有でき、単体テストも容易になります。関心ごとの分離(Separation of Concerns)に貢献します。
  • 3. TypeScriptとの親和性が高い
    Composition APIでは変数や関数単位で型定義が行えるため、TypeScriptとの統合がスムーズです。型補完や静的解析の恩恵を受けやすくなります。
  • 4. 複雑な状態管理が明快になる
    コンポーネントのロジックが増えても、setup関数内で明示的に管理できるため、状態の流れや依存関係を追いやすくなります。

⚠️ 注意点・デメリット

  • 1. 学習コストがやや高い
    Vue初心者や従来のOptions APIに慣れている開発者にとって、refreactivesetup()の概念は最初は取っ付きにくいかもしれません。
  • 2. setup()内が肥大化しやすい
    機能の抽出を行わずにすべてのロジックをsetup内に詰め込むと、逆にコードの可読性が低下します。適切なComposition Functionによる分離が求められます。
  • 3. boilerplateコードの増加
    ref.valueアクセスや、状態の初期化、関数の返却処理など、初心者にとっては冗長に感じるコードが増える傾向があります。
  • 4. 単純なコンポーネントにはオーバースペック
    小さなコンポーネントやUIパーツなどでは、Options APIの方が簡潔に実装できる場合があります。目的に応じた選択が重要です。

🧩 適材適所での活用が鍵

Composition APIは万能ではなく、あくまでも選択肢の一つです。プロジェクトの規模、開発メンバーの習熟度、将来的な拡張性を考慮したうえで、必要な箇所に絞って導入するのが理想的です。

次のセクションでは、実際にどのような場面でComposition APIを選択すべきか、その判断基準をプロジェクト規模や開発体制に基づいて紹介していきます。


6. Composition APIを選ぶべきタイミングとは?

Vue 3では、Composition APIとOptions APIのどちらも正式にサポートされており、開発者はプロジェクトやチームの状況に応じて自由に選択できます。どちらが「正しい」ということではなく、「どちらがより適しているか」を判断することが重要です。このセクションでは、Composition APIを導入すべきシーンを具体的に見ていきましょう。

📌 プロジェクトの規模別の判断基準

  • 小規模プロジェクト:
    UIがシンプルで、複雑な状態管理やロジックが不要な場合は、Options APIの方が学習コストが低く、コードも簡潔に保てます。特にプロトタイピングや個人開発では十分です。
  • 中〜大規模プロジェクト:
    状態管理、非同期通信、ロジックの再利用などが重要になる場面では、Composition APIの柔軟性が大きな武器になります。コードを機能単位で整理しやすく、保守性・拡張性が格段に向上します。

👥 チームの技術スタックと構成

チームにVue 2時代のOptions APIに慣れているメンバーが多い場合は、段階的な導入が理想です。逆に、React HooksやTypeScript、関数型思考に親しんだ開発者が多いチームであれば、Composition APIは直感的に受け入れやすく、パフォーマンスの高い開発が可能になります。

🧪 テスト戦略や拡張性のニーズ

ユニットテストやE2Eテストを重視するチーム、あるいは将来的に機能追加やリファクタリングを頻繁に行う予定がある場合は、Composition Functionによるロジックの切り出しと分離が大きな利点になります。

🔄 混在構成(Options API + Composition API)の活用

Vueでは、1つのコンポーネント内でOptions APIとComposition APIを併用することが可能です。従来のOptions構文の中に、必要なロジックだけComposition Functionで導入することで、段階的にモダンなアーキテクチャへ移行できます。

export default {
  data() {
    return { message: 'こんにちは Vue' };
  },
  setup() {
    const { count, increment } = useCounter();
    return { count, increment };
  }
};

このように、既存のコード資産を活かしつつ、必要なところから無理なく新しいAPIへと展開できるのは、Vueならではの柔軟性の表れです。

つまり、Composition APIの採用は「全面的な切り替え」ではなく、あくまで「必要に応じて導入する構造的戦略」と捉えるべきです。

次のセクションでは、実際の現場でどのようにComposition APIを使ってコンポーネントを設計し、どんな構造が保守性や再利用性を高めてくれるのか、具体的な実装例を交えて解説していきます。


7. 実践例:現場で使えるコンポーネント設計

Composition APIは、その真価を発揮するのがまさに現場での実装フェーズです。ここでは、日常的によく直面する3つのシナリオを取り上げ、それらをどのように構造的かつ再利用可能な形で構築できるか、具体例を通して学びます。

📄 フォーム処理の再利用ロジック

フォームの入力値管理やリセット処理は多くのコンポーネントで繰り返されるロジックです。これをComposition Function化することで、どのフォームでも同じ処理を再利用できます。

// useForm.js
import { ref } from 'vue';

export function useForm() {
  const form = ref({ name: '', email: '' });
  const resetForm = () => {
    form.value = { name: '', email: '' };
  };
  return { form, resetForm };
}

上記の関数を任意のコンポーネント内のsetup()で呼び出すだけで、フォーム管理が一元化され、コードの重複を防げます。

🌐 API通信と非同期データ取得

APIからデータを取得し、その状態を管理する処理も、アプリケーションでは頻出です。以下はaxiosを用いたユーザーデータ取得の例です。

// useFetchUser.js
import { ref } from 'vue';
import axios from 'axios';

export function useFetchUser(userId) {
  const user = ref(null);
  const loading = ref(false);
  const error = ref(null);

  const fetchUser = async () => {
    loading.value = true;
    try {
      const response = await axios.get(`/api/users/${userId}`);
      user.value = response.data;
    } catch (err) {
      error.value = err;
    } finally {
      loading.value = false;
    }
  };

  return { user, loading, error, fetchUser };
}

このような処理をComposition Functionとして分離することで、API通信に伴うローディング、エラーハンドリングを一貫して管理できます。

📦 グローバル状態管理(Vuex不要)

VuexやPiniaを使わずに、簡易的なグローバル状態をComposition APIで構築することも可能です。以下はカウンターの状態管理の例です。

// useCounterStore.js
import { ref } from 'vue';

const count = ref(0);
const increment = () => count.value++;
const decrement = () => count.value--;

export function useCounterStore() {
  return { count, increment, decrement };
}

このComposition Functionを複数のコンポーネントでimportして使用すれば、カウンター状態を共有できます。スモールスケールのグローバル状態管理に非常に有効です。

🛠️ 設計時の注意点

  • 関心ごとの分離: 機能単位でロジックを抽出し、再利用性を意識しましょう。
  • 抽象化のやりすぎに注意: 必要以上に細かく関数を分けすぎると、かえって可読性が損なわれることがあります。
  • 命名規則の一貫性: useXxxの形式で命名することで、意図が明確になり、チーム開発でも認識が統一されます。

このように、Composition APIは単なる文法ではなく、「ロジックをどのように整理するか」という設計思想に強く結びついています。次のセクションでは、Vue.jsの将来におけるComposition APIの役割と意義について考察します。


8. 結論:Vue.jsの未来とComposition APIの位置づけ

Composition APIは、Vue.jsにおける単なる新しい構文以上の存在です。それは、現代的なフロントエンド開発において求められる「モジュール性」「再利用性」「保守性」といったアーキテクチャ上の課題に対する、Vueチームからの明確な回答であり、Vueエコシステムの未来を形作る中心的な考え方でもあります。

Options APIと比べて、Composition APIは「構造化された設計」を志向しています。これにより、コードが複雑になっても論理的に整理しやすく、プロジェクトが拡大しても見通しの良い状態を保てるようになります。特にTypeScriptとの親和性や、テストしやすいコードの構成といった点では、現代のフロントエンド開発において非常に大きなアドバンテージとなります。

とはいえ、Vueチーム自身も「Options APIを廃止する」といった立場は取っておらず、むしろ状況に応じて両方を適切に使い分けることを推奨しています。実際の現場でも、Options APIのわかりやすさとComposition APIの構造性を組み合わせたハイブリッドなアプローチが効果を上げています。

今後、Vue.jsの公式ドキュメントやエコシステム、外部ライブラリなども、徐々にComposition APIを前提とした設計へと移行していくことが予想されます。これに対応できるよう、今のうちから段階的に習熟しておくことは、開発者としての武器になるでしょう。

技術とは選択の連続であり、選択とは設計そのものです。あなたのVueアプリケーションは、どんな構造を持っていますか?そして、これからどんな未来を描きたいですか?

댓글 남기기

Table of Contents