Vue 3.xの新機能、Composition APIを触ってみた

image.png (48.1 kB)

TOC

2020年9月18日にVueのv3.0.0が正式にリリースされました。 そこで搭載された新機能であるComposition APIを、今更ではありますが使ってみたいと思います。

環境構築

Vue3を利用できるようにする方法はCLIを利用するなどいくつかありますが、今回は公式ドキュメントでも取り上げられていたViteというビルドツールを利用して環境構築をしてみたいと思います。

Viteとは

ViteはVue.jsの作者のEvan You氏が開発しているビルドツールです。 Vue.jsだけでなくReact・Preactの環境も構築することができ、またTypeScriptにも対応させることができます。 特徴としてはES モジュールをそのままインポートするため、バンドル処理がなく高速に動作するとのことです。

インストール

では早速ターミナルからコマンドを入力しインストールを行います。

$ npm init @vitejs/app sample-project

入力後、どのtemplateを利用するか選択するする項目が出るので、今回はvue-tsを選択します。

Scaffolding project in /Users/takumi/Desktop/vue3/sample-project...
? Select a template: … 
  vanilla
  vue
❯ vue-ts
  react
  react-ts
  preact
  preact-ts
  lit-element
  lit-element-ts

完了後、ディレクトリに移動し、パッケージのインストールを行い開発環境を起動します。

$ cd sample-project
$ npm install
$ npm run dev
image.png (79.4 kB)

Vue3を扱う環境が完成しました!

Composition APIとは

では実際にコードを書きながらComposition APIがどのようなものか体験していきましょう。

公式サイトによるとComposition APIは、

Introducing the Composition API: a set of additive, function-based APIs that allow flexible composition of component logic

https://vue-composition-api-rfc.netlify.com/#summary

「コンポーネント ロジックの柔軟な構成を可能にする、関数ベースの相加的な API」との事で、TypeScriptを使用した時の型推論の改善や、コードの可読性・共通化がしやすくなるそうです。

setupコンポーネントオプション

Composition APIで新しく追加されたコンポーネントオプションで、インスタンスが作成される前に呼び出され、コンポーネントのエントリーポイントとなるオプションです。 インスタンス前に生成されるのでこの中にthisはありません。 つまり、渡されてきたpropsを除いたコンポーネント内で定義されているcomputedmethodsにはアクセスする事ができないということになります。

では実際にコンポーネントにsetupを使ってみましょう。 まずsrc/componentsディレクトリにSample.vueを作成し、src/App.vueから呼び出します。

src/App.vue
<template>
  <Sample />
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import Sample from './components/Sample.vue'

export default defineComponent({
  name: 'App',
  components: {
    Sample
  }
})
</script>

コンポーネントを作成

はじめにdefineComponentを使いコンポーネントを作成します。 defineComponentの中身的は渡されたオブジェクトをただ返すだけのようですが、defineComponentからコンポーネントを作成する事で自然に型推論が効くようになります。 TypeScriptを利用していない場合は使用する必要はありません。

Sample.vue
<script lang="ts">
import { defineComponent} from "vue";

export default defineComponent({
  setup() {},
})
</script>

リアクティブプロパティの定義

setup内でのリアクティブプロパティの定義は、ref関数とreactive関数を使い定義を行います。 この2つの関数は登録する値によって使い分けを行います。

  • ref ... プリミティブ値
  • reactive ... オブジェクト

定義した値をtemplateで利用する場合は必ずreturnをする必要があります。

Sample.vue
<template>
  <div>
    {{ count }}回目の {{ state.text }}!!
  </div>
</template>

<script lang="ts">
import { defineComponent, reactive, ref } from "vue";

export default defineComponent({
  setup() {
    const count = ref<number>(1)
    const state = reactive<{ text: string }>({
      text: 'Vue3'
    })

    return {
      count,
      state
    }
  },
})
</script>

refを使う際の注意点

ref関数で定義したリアクティブプロパティをsetup関数内で変更する時は、property.valueとする必要があります。 先ほど定義した変数countの数をインクリメントするメソッドを作成し、挙動を確認してみましょう。

Sample.vue
<template>
  <div>
    {{ count }}回目の {{ state.text }}!!
  </div>
  <button @click="countIncrement"></button>
</template>

<script lang="ts">
import { defineComponent, reactive, ref } from "vue";

export default defineComponent({
  setup() {
    let count = ref<number>(1)
    const state = reactive<{ text: string }>({
      text: 'Vue3'
    })

    const countIncrement = () => {
      count.value++
    }

    return {
      count,
      state,
      countIncrement
    }
  },
})
</script>

コンソールでcountを確認してみると、RefImplと言うオブジェクトでラップされている事が確認できます。

propsを扱う

propsはdefineComponentに渡すオブジェクト内に設定します。 propsの中身はVue2と変わらずtypeやdefaultなどを定義することができます。

コードをわかりやすくする為、先程まで記述していたcount、 state、countIncrementは削除しておきます。

まずは、親コンポーネントからmessageという文字列を、propsMessageとして子コンポーネントに渡します。

App.vue
<template>
  <Sample :propsMessage="message" />
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue'
import Sample from './components/Sample.vue'

export default defineComponent({
  name: 'App',
  components: {
    Sample
  },
  setup() {
    let message = ref<string>('Propsを利用する')
    return {
      message
    }
  }
})
</script>

子コンポーネントのpropsプロパティに、親コンポーネントから渡されたpropsMessageを定義します。

Sample.vue
export default defineComponent({
  props: {
    propsMessage: {
      type: String,
      required: true,
      default: 'デフォルト値',
    }
  },
  setup() {

setup()内でpropsの値を利用する時は、setup()の第一引数で受け取ります。

Sample.vue
<template>
  <div>
    {{ propsMessage }}
  </div>
</template>

<script lang="ts">
import { defineComponent, reactive, ref } from "vue";

export default defineComponent({
  props: {
    propsMessage: {
      type: String,
      required: true,
      default: 'デフォルト値',
    }
  },
  setup(props) {
    console.log(props.propsMessage) // Propsを利用する
  },
})
</script>

emitを扱う

emitを利用する場合はsetup()の第二引数に渡されるcontextから呼び出します。 今回は親コンポーネントのmessageを変更するchange-messageというemitを作成し、実際に動かしてみましょう。

Sample.vue
<template>
  <div>
    {{propsMessage}}
  </div>
  <button @click="changeMessage">テキストを変更</button>
</template>

<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  props: {
    propsMessage: {
      type: String,
      required: true,
      default: 'デフォルト値',
    }
  },
  setup(props, context) {
    const changeMessage = () => {
      context.emit('change-message', 'emitを利用してテキストを変更しました')
    }
    return {
      changeMessage,
    }
  },
})
</script>

また、Vue3からはpropsのように、コンポーネント内で利用するemitも明示的に宣言をしてあげないと警告が出るようになりました。 宣言の仕方は、emitsオプションに利用するemit名を配列で定義します。

Sample.vue
export default defineComponent({
  props: {
    propsMessage: {
      type: String,
      required: true,
      default: 'デフォルト値',
    }
  },
  emits: ['change-message'], // 追加
  setup(props, context) {
    const changeMessage = () => {
      context.emit('change-message', 'emitを利用してテキストを変更しました')
    }
    return {
      changeMessage,
    }
  },
})

emitで渡された値を親コンポーネント内で受け取ります。

App.vue
<template>
  <Sample :propsMessage="message" @change-message="changeMessage" />
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue'
import Sample from './components/Sample.vue'

export default defineComponent({
  name: 'App',
  components: {
    Sample
  },
  setup() {
    let message = ref<string>('Propsを利用する')
    const changeMessage = (emitedMessage: string) => {
      message.value = emitedMessage
    }
    return {
      message,
      changeMessage
    }
  }
})
</script>

ライフサイクルフック

setup()でのライフサイクルフックは以下のように置き換わります。

Options API setup()
beforeCreate -
created -
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
errorCaptured onErrorCaptured
renderTracked onRenderTracked
renderTriggered onRenderTriggered
export default {
  setup() {
    onMounted(() => {
      console.log('コンポーネントがマウント時に実行されます')
    })
  }
}

感想

非常に簡単ではありますが、Composition APIを実際に触って動かしてみました。 setup()内で完結できるようになった為、1つのロジックに対し、data/computed/methodsなど上から下まで読まないといけないということがなくなり、見通しやすいコードが描けるようになるのではないかなと思っています。 まだまだ触りの部分しか調べられていないので、実際にサンプルアプリなどを作ってみて、書き味の違いをもっと体験してみたいなと思いました。

参考

https://v3.vuejs.org/guide/composition-api-introduction.html#setup-component-option https://qiita.com/eyuta/items/9a81a1697f55fbde4bdc https://qiita.com/ryo2132/items/f055679e9974dbc3f977 https://qiita.com/mgr/items/a5e35636d371969e0a4d

CONTACT
© 2023, Kakkiii All Rights Reserved.