雑食日誌

Vue.jsとServerless。ときどきチーム開発

Vue Composition APIとTypeScriptの組み合わせ

この記事はギルドワークスAdvent Calendarの3日目の記事です。 Vue.jsのバージョン3にてリリース予定のComposition APIとTypeScriptを組み合わせについて紹介します。

adventar.org

Composition APIとは

Composition APIは関数ベースでコンポーネントを実装する機能です。関数ベースのため、機能の再利用性を高める役割を持っています。他のライブラリではReact hooksやSvelteに近い機能です。

公式のRFCに記載されているモチベーションの1つにTypeScriptとの相性を高めることが書かれています。本記事ではサンプルコードを踏まえてなぜTypeScriptと相性がよくなるのか考えてみます。

https://vue-composition-api-rfc.netlify.com/#better-type-inference

準備

Vue2系で試す場合はプラグインが提供されているので、インストールする。

https://github.com/vuejs/composition-api

$ npm install @vue/composition-api --save
or
$ yarn add @vue/composition-api

インストールが完了したらVue.use を追加します。

import Vue from 'vue';
import VueCompositionApi from '@vue/composition-api';

Vue.use(VueCompositionApi);

TypeScriptと併せて使用する、かつVS CodeでVeturを使用している場合、設定を以下のように変更する必要があります。

{
  "vetur.useWorkspaceDependencies": true
}

Options APIとComposition APIの比較

TypeScriptとの併用がやりやすくなるかを見るために、基本的なVue.jsのAPIであるOptions APIと比較してみたいと思います。 今回は両方のAPIを使って同じ機能を持ったモーダルコンポーネントを実装してみます。 機能としては以下になります。

  • モーダルの表示・非表示ができる
  • モーダル内に日付を表示し、ボタンによってカウントアップできる
  • 日付が条件に達したらメッセージを表示

f:id:keinumata:20191202225611p:plain
メッセージ無

f:id:keinumata:20191202225508p:plain
メッセージ有

以下のGitHubにても公開しています。

https://github.com/keinuma/compare-options-and-composition

テンプレート

テンプレートおよびスタイルは共通のコードを使用します。(スタイルのコードは今回の記事では紹介しません。)

<template>
  <div>
    <div :class="{ 'modal-overlay': showModal }">
      <div
        v-if="showModal"
        class="modal-isshow modal-open"
      >
        <div class="modal-container">
          <button class="close-btn btn btn-outline-info" @click="closeModal">
            閉じる
          </button>
          <slot></slot>
          <div>
            <div v-show="isLastDate" class="alert alert-danger" role="alert">
              <p class="alert-last-date">
                クリスマスになりました。<br />
                アドベントカレンダーの最終日です。
              </p>
            </div>
            <p>この記事はギルドワークスアドベントカレンダーの{{ date }}日目の記事です。</p>
            <button
              type="button"
              class="btn btn-info"
              @click="countUpDate" 
              :disabled="isLastDate">
              次の日
            </button>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

Options API

Options APIの場合は以下になります。 TypeScript対応は Vue.extend を使用しています。

import Vue from 'vue'

export default Vue.extend({
  name: 'ModalOptions',
  props: {
    showModal: {
      type: Boolean,
      required: true,
      default: false
    }
  },
  data() {
    return {
      date: 1
    }
  },
  computed: {
    isLastDate() {
      return this.date === 25
    }
  },
  methods: {
    countUpDate() {
      if (this.isLastDate) {
        return
      }
      this.date++
    },
    closeModal(e: Event) {
      e.stopPropagation()
      this.date = 1
      this.$emit("onCloseModal")
    }
  }
})

Composition API

Composition APIの場合は以下になります。 基本的に setup 関数の中でデータやメソッドの定義を行います。 リアクティブなデータはComposition APIで定義されている refreactive 関数から定義できます。 モーダルを閉じるメソッドで使用しているように、Vue インスタンスへは context を使用してアクセスできます。

import {
  createComponent,
  ref,
  computed
} from '@vue/composition-api'

type Props = {
  showModal: boolean
}

export default createComponent({
  props: {
    showModal: {
      type: Boolean,
      required: true,
      default: false
    }
  },
  setup(props: Props, context) {
    const date = ref(1)
    const isLastDate = computed(() => {
      return date.value === 25
    })
    const countUpDate = () => {
      if (isLastDate.value) {
        return
      }
      date.value++
    }
    const closeModal = (e: Event) => {
      e.stopPropagation()
      date.value = 1
      context.emit("onCloseModal")
    }
    return {
      showModal: props.showModal,
      date,
      isLastDate,
      countUpDate,
      closeModal
    }
  }
})

考察

2つのコードを比較すると、Composition APIはOptions APIに比べて this がなくなっています。 これまで Vuethis にはデータやメソッドのような開発者が定義したアプリケーション的意味合いと、filter やVue RouterなどのVueインスタンス自身の2つの意味を持っていました。Composition APIの場合、前者を setup 関数内で定義、後者を context に持たせることで this の責務を分離しています。 責務を分離することで型定義を推論を効かせしやすくしていると考えられます。

また、 Options APImethods でも同じことはできるのですが、相互参照が見えやすい setup 関数だからこそ機能としての再利用性を意識しやすくなります。そのため、外部モジュールへの抽出や構造化が促進されると考えられます。

まとめ

  • Composition APIによってTypeScriptが使いやすくなる
  • Vue 3待ち遠しい