Vue Composition APIのメリットを解説!Options APIとComposition APIの違い

2020年9月18日に「Vue.js v3.0」がリリースされ、その中でも特にVue Composition APIが注目されています。

Composition APIの使い方が分かったあとは、なぜ使うのかというところが気になりますよね。

この記事ではComposition APIを使用するとどのようなメリットが得られるのかを解説していきます。

もし、まだComposition APIを使ったことがない場合は、以下の記事で解説していますので、気になる方は是非ご覧ください。

関連記事

2020年9月18日に「Vue.js v3.0」がリリースされ、その中でも特にVue Composition APIが注目されています。 Composition APIでは今まで慣れ親しんできたVue2の書き方から一新[…]

Vue2(Options API)のデメリット

Vue2のOptions APIのデメリットは公式ドキュメントの「Motivation」という章で解説されています。

この章はさらに「Logic Reuse & Code Organization」と「Better Type Inference」という節に分かれています。

それぞれの節でVue2のOptions APIが抱えていた問題について言及されていて、「Logic Reuse & Code Organization」では2つ、「Better Type Inference」では1つの問題が提起されています。

つまり、Composition APIはこれらの問題を解決するために策定されました。

ロジックの再利用とコード構成

ロジックの再利用とコード構成については公式ドキュメントの以下の部分で言及されています。

この章ではVue2でのOptions APIが抱える問題が2つ述べられています。

コンポーネント間でのロジック再利用性の欠如

1つ目の問題はロジックの再利用に関するものです。

Lack of a clean and cost-free mechanism for extracting and reusing logic between multiple components.

翻訳すると以下のような感じです。

複数のコンポーネント間でロジックを抽出して再利用するためのクリーンでコストのかからないメカニズムの欠如。

Options APIではそれぞれのコンポーネントのオプションに直接処理を書いていたので、確かに再利用は難しかったですね。

このロジックを再利用するメカニズムの欠如が次のコードの可読性の低下の問題に繋がっています。

コードの可読性の低下

2つ目は可読性に関するものです。

The code of complex components become harder to reason about as features grow over time.
This happens particularly when developers are reading code they did not write themselves.
The root cause is that Vue’s existing API forces code organization by options, but in some cases it makes more sense to organize code by logical concerns.

翻訳すると以下のような感じです。

複雑なコンポーネントのコードは、機能が時間の経過とともに大きくなるにつれて、読み解くのが難しくなります。
これは特に、開発者が自分で作成していないコードを読んでいるときに発生します。
根本的な原因は、Vueの既存のAPIがオプションによるコード構成を強制することですが、場合によっては関心事ごとにコードを分割して構成する方が理にかなっています。

Options APIでは1つのコンポーネントの中に全てのオプションを記述しなければいけなかったので、行数が多くなってくると、確かに読みづらかったですね。

例えば他人が書いたコードを読まなければいけない際に、こんなコンポーネントだった場合には、読み解くのに相当苦労しそうです。

なぜこのように読みづらいコードになってしまっているかというと、1つ目の問題で出てきたロジック再利用性の欠如が原因で、関心事によってコードを分割することができないからです。

このコードを関心事ごとに色分けしてみると以下のようになります。

Vue Options API感心事ごとに色付け

引用元: https://composition-api.vuejs.org/

このように関心事が断片化された状態に対して公式ドキュメントでは以下のように言及されています。

Such fragmentation is exactly what makes it difficult to understand and maintain a complex component.
The forced separation via options obscures the underlying logical concerns.
In addition, when working on a single logical concern we have to constantly “jump” around option blocks to find the parts related to that concern.

翻訳すると以下のような感じです。

このように関心事が分離されていると、複雑なコードを理解してメンテナンスするのを困難にします。
オプションの使用の強制はこのように関心事を覆い隠してしまいます。
さらに単一の関心事に取り組む場合、その関心事に関する部分を見つけるためにコード間を「ジャンプ」する必要があります。

まとめ

Vue2のOptions APIではロジックの再利用とコードの構成に関して以下の2つの問題を抱えていました。

  • 簡単にロジックを再利用するためのメカニズムの欠如
  • オプションの利用によるコード構成の強制で可動性が低下

型インターフェースの改善

型インターフェースの改善については公式ドキュメントの以下の部分で言及されています。

規模の大きなプロジェクトではTypeScriptが採用されることも多いと思いますが、Vue2のOptions APIはTypeScriptと相性が良くありませんでした。

相性が良くなかった主な原因はthisです。

公式ドキュメントでは以下のように言及されています。

The use of this in a Vue component is a bit more magical than plain JavaScript (e.g. this inside functions nested under methods points to the component instance rather than the methods object). In other words, Vue’s existing API simply wasn’t designed with type inference in mind, and that creates a lot of complexity when trying to make it work nicely with TypeScript.

翻訳すると以下のような感じです。

Vueコンポーネントの中でthisを使用することはプレーンなJavaScriptでの使用と比べると少し複雑です。
(例えば、methodsオプションの中で定義した関数の中のthisはmethodsオブジェクトではなくコンポーネントインスタンスを参照します。)
言い換えると、Vue2のAPIは型インターフェースを念頭において設計されていなかったため、TypeScriptと併用しようとすると非常に複雑になります。

コンポーネント内でのthisの参照先の決め方がプレーンなJavaScriptのルールと異なるため、型インターフェース(TypeScript)やIDEと相性が悪く、その恩恵が受けられないということですね。

まとめ

Vue2のOptions APIでは型インターフェースとの統合に関して以下の問題を抱えていました。

  • thisの参照先が複雑で型インターフェースの恩恵を受けられない

Vue Composition APIのメリット

Vue2のOptions APIのデメリットの章でOptions APIが抱えていた問題は以下の3つであることを見てきました。

  • 簡単にロジックを再利用するためのメカニズムの欠如
  • オプションの利用によるコード構成の強制で可動性が低下
  • 型インターフェースの恩恵を受けられない

Composition APIではこれらの問題が全て解決されています。

まずはロジックの再利用・可読性の改善から見ていきます。

ロジックの再利用・可読性の改善

Vue2のOptions APIではロジック再利用のためのメカニズムが提供されていないために、関心事がコンポーネントのあらゆる箇所に分散してしまい、可動性の低下を招いているのでした。

Composition APIでは以下のようにしてこの問題を解決しています。

  • ロジック再利用のメカニズム(合成関数)を提供
  • 合成関数を用いて関心事によって分割されたロジックを併置

まずは合成関数から見ていきます。

合成関数といっても実態は普通の関数です。

例えば新規フォルダ作成機能は以下のように記述することができます。

function useCreateFolder() {
  const showNewFolder = ref(false);
  const newFolderName = ref("");

  const newFolderValid = computed(() => isValidMultiName(newFolderName.value));

  async function createFolder() {
    // 新規フォルダ作成処理
  }

  return {
    showNewFolder,
    newFolderName,
    newFolderValid,
    createFolder,
  };
}

新規フォルダ作成に関するロジックが全てこの関数内に記述され、カプセル化されていることに注目してください。

このように関心事をカプセル化した関数をComposition APIでは「Composition Function(合成関数)」と呼んでいます。

合成関数はそれが合成関数であることを示すために関数名をuseで始めることが公式ドキュメントで推奨されています。

さらにこの合成関数をコンポーネント内の全ての関心事に対して適用すると、関心事によって分割したロジックを併置することができます。

function useCurrentFolderData(networkState) { // ...
}

function useFolderNavigation({ networkState, currentFolderData }) { // ...
}

function useFavoriteFolder(currentFolderData) { // ...
}

function useHiddenFolders() { // ...
}

function useCreateFolder(openFolder) { // ...
}

あとはこの合成関数をどのようにしてコンポーネント内で使用するかですが、setup関数をエントリーポイントとして合成します。

export default {
  setup () {
    // Network
    const { networkState } = useNetworkState()

    // Folder
    const { folders, currentFolderData } = useCurrentFolderData(networkState)
    const folderNavigation = useFolderNavigation({ networkState, currentFolderData })
    const { favoriteFolders, toggleFavorite } = useFavoriteFolders(currentFolderData)
    const { showHiddenFolders } = useHiddenFolders()
    const createFolder = useCreateFolder(folderNavigation.openFolder)

    // Current working directory
    resetCwdOnLeave()
    const { updateOnCwdChanged } = useCwdUtils()

    // Utils
    const { slicePath } = usePathUtils()

    return {
      networkState,
      folders,
      currentFolderData,
      folderNavigation,
      favoriteFolders,
      toggleFavorite,
      showHiddenFolders,
      createFolder,
      updateOnCwdChanged,
      slicePath
    }
  }
}

このようにsetup関数内に全ての処理を記述するのはOptions APIと比べて冗長な気がしてしまいますが、以下のようなメリットもあります。

  • setup関数を見ればそのコンポーネントが何をしようとしているかを理解できる
  • return文1箇所だけを見ればtemplateに公開されているものを確認できる

Options APIではコンポーネント全体を見渡さなければ、そのコンポーネントが何をしようとしているのかを理解できませんでした。

また、「何がtemplateに公開されているのか」もOptions APIではdata・computed・methods…などコンポーネント内の複数箇所を「ジャンプ」しながら確認する必要がありました。

さらにOptions APIとは関係ありませんが、setup関数内に全ての処理を記述すると、以下のメリットも得られます。

  • 合成関数の引数に何が渡されているかを見れば合成関数同士の依存関係が分かる

ここまで見てきた合成関数を導入すると「コードの可読性の低下」の節で見たコードは以下のように改善されます。

Vue Options APIとComposiiton API感心事ごとに色付け

引用元: https://composition-api.vuejs.org/

関心事によって分割されたロジックが併置されていて非常に読みやすくなっています。

型インターフェースの改善

Options APIでは参照先の決定ルールが複雑なthisに依存する必要があったため、型インターフェースの恩恵を受けることができませんでした。

Composition APIでは、ほとんどが単純な変数と関数で構成されているため、型インターフェースの恩恵を最大限受けることができます。

例えばComposition APIで以下が使われた場合を考えてみます。

  • ref
  • reactive
  • computed
  • watch
  • methods
  • ライフサイクルフック

これらは全てsetup関数のスコープで使用されるため、他の変数や関数を参照する際にthisを使用する必要がありません。

import { ref, reactive, computed, watch, onMounted } from "vue";

export default {
  setup() {
    const countRef = ref(1);

    const increment = () => {
      // thisを使用せずに参照できます
      countRef.value++;
    };

    const doubleCount = computed(() => {
      // thisを使用せずに参照できます
      return countRef.value * 2;
    });

    const obj = reactive({
      count: 1,
    });

    const decrement = () => {
      obj.count--;
    };

    watch(
      () => obj.count,
      () => {
        // thisを使用せずに参照できます
        console.log(countRef.value);
      }
    );

    onMounted(() => {
      // thisを使用せずに参照できます
      console.log(countRef.value);
    });
  },
};

このようにComposition APIではthisに依存する必要がなくなり、型インターフェースの恩恵を受けられるようになっています。

まとめ

今回はVue Composition APIのメリットについて見てきました。

ここまで見てきたようにComposition APIには以下のメリットがあることが分かりました。

  • ロジックを再利用するためのメカニズムの提供
  • 合成関数の導入による可読性の向上
  • 型インターフェースの改善

これらは全てOptions APIで問題として提起されていたものを改善するために提案されました。

Composition APIを効率的に使いこなしていくためにも、設計の段階からメリットを意識していきたいですね。

それでは、ここまでご覧いただきありがとうございました。