メドピア開発者ブログ

集合知により医療を再発明しようと邁進しているヘルステックカンパニーのエンジニアブログです。読者に有用な情報発信ができるよう心がけたいので応援のほどよろしくお願いします。

小さくはじめる Vue の Composable

こんにちは。フロントエンドエンジニアの小林和弘 @kzhrk0430 です。

今日は、Vue の機能のひとつである Composable を導入してみた体験談をシェアしようと思います。Vue を使っている方にはおなじみの機能かもしれませんが、僕が所属するチームでは Composable があまり積極的に利用されていない状況だったので Composable を小規模に導入したお話をします。

Composable とは

Vue における Composable の基本的な概念と役割について説明します。

Vue 2 では機能ごとのオプション(data, methods, computed, etc…)を宣言する Options API という書き方が使われていました。この書き方は Vue の機能の関心事をまとめる役割を果たしていました。

export default {
    data() {},
    computed: {},
    methods: {},
    mounted() {}
    // ...他のオプション
}

Vue 2.7 以降、Options API とは異なる新しい Vue の書き方として Composition API が組み込まれました。Composition API では Options API で宣言していた各オプションが関数として実装されています。

Composable はこの Composition API を活用して、Options API では実現できなかった Vue の処理をまとめて再利用するための関数です。

なぜ Composable が利用されていなかったのか

チーム内で Composable があまり積極的に利用されていなかった理由としては、

  • 各画面が独自性の高い機能を持っていたため、処理を共通化する必要性が低かったこと
  • 画面を跨ぐ機能を持つものはコンポーネント(Atomic Design の organisms)としてきれいに切り出されていたこと

が考えられます。

他には Composable を多用することでコンポーネント間で共通化する必要がない処理まで外部ファイル化されて、Vue が持っている良さが損なわれてしまうことを懸念していました。

ここでいう Vue が持っている良さというのは単一ファイルコンポーネント(SFC: Signle File Component)と呼ばれる独自のファイル形式です。

Vue は *.vue という特殊なファイル形式をもっています。.vue ファイル内で script, template, style タグでそれぞれ JavaScript, HTML, CSS を管理することができます。

Composable を徹底しすぎると共通利用されない処理が SFC の外側に定義されて、SFC で処理が一覧化できていた vue ファイルの良さが損なわれると考えています。

すでに reactive で関心事をまとめていた

Composable を導入する前に、チーム内では Vue の reactive 関数を利用してコンポーネント内の関心事をまとめるという取り組みを行っていました。

reactive 関数は引数に渡した Object をリアクティブオブジェクトとして扱うことができます。これを利用して、コンポーネント内の機能をリアクティブオブジェクトに集約していました。

具体的にどのようなことをやっていたか説明します。

下記のキャプチャは実際に kakari というかかりつけ薬局を支援する SaaS で利用されている、薬局の臨時休業・臨時営業時間を設定するモーダルのコンポーネントです。

kakari で実際に使われている薬局の臨時休業・臨時営業時間を設定するモーダル

このコンポーネントでは 3 つのリアクティブオブジェクトを作成しています。

  • 営業時間の表示を管理する openingHour
  • 処方せん受付時間を管理する prescriptionReceivableHour
  • チャット受付時間を管理する chatReceivableHour

リアクティブオブジェクトは各時間の状態管理とイベントハンドラ管理を行っています。

営業時間表示コンポーネント、処方せん受付時間コンポーネント、チャット受付時間コンポーネントを作成して関心事を切り出すという方法もありますが、下記の理由からひとつのコンポーネント内で reactive で状態管理をしています。

  • コンポーネント化しても、親コンポーネント側で子コンポーネントの状態管理が必要
  • 他のコンポーネントで利用されるような汎用的なコンポーネントではない

リアクティブオブジェクトに関心事がまとまるのでコードの見通しはよくなりました。しかし、reactive 関数をあまり利用しない方がよいというのが昨今の Vue の流れです。

ref vs reactive

Vue Fes Japan 2023 のイベントの Vue.js クリニックで Vue の作者である Evan You が refreactive のどちらを使うのが良いのかという質問に対して、基本的には ref を使って欲しいという回答をしていました。

reactive の難点としては、reactive 関数の返り値を代入した変数がリアクティブオブジェクトなのか通常の Object なのか判別できないということが上げられます。

Composable で関心事をまとめる

Composable の話に戻ります。

reactive のリアクティブオブジェクトで関心事をまとめていましたが Object と判別がつかないという問題があったため、Composable でその役割を代替することにしました。

具体的な例を出します。

下記は Nuxt のページコンポーネント内で表示するフォームコンポーネントの内容を切り替えるための Composable です。

<script setup>
import { computed, ref } from 'vue';

function useForm() {
  const formType = ref<'auth' | 'message' | 'reply' | 'complete'>('auth');

  const isAuth = computed(() => formType.value === 'auth');
  const isMessage = computed(() => formType.value === 'message');
  const isReply = computed(() => formType.value === 'reply');
  const isComplete = computed(() => formType.value === 'complete');

  function moveToMessage() {
    formType.value = 'message';
  }
  function moveToReply() {
    formType.value = 'reply';
  }
  function moveToComplete() {
    formType.value = 'complete';
  }

  return {
    isAuth,
    isMessage,
    isReply,
    isComplete,
    moveToMessage,
    moveToReply,
    moveToComplete,
  };
}

// 中略: Composable 以外のコンポーネントの処理

const {
  isAuth,
  isMessage,
  isReply,
  isComplete,
  moveToMessage,
  moveToReply,
  moveToComplete,
} = useForm();
</script>

フォームコンポーネントの切り替えに利用している computed やメソッドが Compoasble にグルーピングされています。Composable 内でのみ参照している formType も外部に露出しなくなっています。

標準的な Composable の使い方であればuseForm 関数を外部ファイル化しますが、共通化する必要性がないものなのでページコンポーネントの script setup 内で useForm 関数を定義しています。

今回の例は独自性の高い機能なので Composable を共通化することは難しいですが、関心事をまとめた Composable を作成しておくことで将来的にその関心事を他のコンポーネントで流用したくなったときに関数を切り出すだけですぐにロジックを共通利用できます。

まとめ

関心事をまとめるという目的で Composable を小規模に導入してみたというお話でした。

Composable の主な利用方法は、サードパーティー JavaScript やライブラリ、Web API の処理を外部化して Vue コンポーネントのテストを書きやすくしたり、まとめたロジックを再利用することだと思っています。

今回紹介した関心事をまとめるという目的で Composable を利用するという話は公式ドキュメントにもコード整理のためのコンポーザブル抽出として説明がされていますが、SFC の良さを活かすために script setup 内で Composable を定義しているのが今回のお話でした。

なぜここまで関心事をまとめることに熱心になるのか考えてみましたが、Vue の公式ドキュメントの Composition API と Options API のトレードオフの話を読んでしっくりきました。

Options API は各オプションでコンポーネントの機能をグルーピングしてくれていましたが、Composition API では関数を宣言するだけの、いわば通常の JavaScript を書くときと同じようにコンポーネントの処理を書くようになりました。

Options API では定められたコードパターンによって秩序が保たれていましたが、Composition API にはその秩序がないため、reactive や Composable を活用して秩序をつくり出している状態であると想像しています。


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。

ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note

style.medpeer.co.jp