vue3.svg

【Vue 3】Composition API の基本

Vue 3

はじめに

※ Vue 2の知識があることを前提の記事となっています。

今回の題材である Composition API 導入により、Vueの<script>部分の記述が大きく変わります。 Vue 2からVue 3への変更で一番大きなものは Composition API の導入と言っても過言ではないでしょう。

Composition APIは、Vue 2でも拡張機能として導入することはできましたが、多くのユーザーは基本的な Options API を使用していたはずです。 Options APIでは、オブジェクトプロパティとしてdatamethodsなどの役割ごとにまとめて記述していました。

Options API
<script>
export default {
  data: () => ({
    count: 0,
  })
  methods: {
    increment() {
      this.count++;
    },
    decrement() {
      this.count--;
    }
  }
}
</script>

これは Composition API を使用すると以下のように変わります。

Composition API
<script setup>
import { ref } from 'vue';

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

見てわかるようにdatamethodsなどの記述がなくなりスッキリした印象です。 この記述の変化により、TypeScriptがかなり扱いやすくなるそうです。(この記事では使用しません。)

念のための補足として、Vue 3になったからといって Composition API を使用しないといけないということはありません。 これまで通り Options API も使用できるので、自身で良い方を選択してください。

では、具体的に Composition API の使用方法について見ていきましょう。

setup

Composition API を使用するには、通常の<script>ではなく<script setup>を使用します。

<script setup>が利用できるのは Vue 3.2〜になります。 それまではsetup()を使用していましたが、公式が<script setup>を推奨しているため、この記事では<script setup>のみ説明します。

<script setup>ではexport default {}などの記述は不要で、直接データやメソッドなどの定義ができます。

<template>
  <div>{{ text }}</div>
  <button @click="onClick">click</button>
</template>

<script setup>
const text = 'Hello World!!';
const onClick = () => { console.log('Clicked!!') };
</script>

しかし、この記述だとtextはただの固定値になります。 仮にletにして値を変更してもDOMには展開されません。 要はtextはリアクティブではないのです。

変数をリアクティブとするためには次に説明するrefreactiveを使用します。

ref, reactive

上記のtextをリアクティブにするには、下記のようにrefを使用します。

import { ref } from 'vue';
const text = ref('Hello World!');

refなどComposition APIに関するものはvueからimportして使用します。

refで定義した変数の値は、valueプロパティによってアクセスします。 text = 'Changed Text!!'のように直接アクセスすることはできません。

const change = () => text.value = 'Changed Text!!';

<template>への記述ではvalueは不要です。

<div>{{ text }}</div>

上記は文字列でしたが、同様にオブジェクトもリアクティブにしてみます。

import { ref } from 'vue';

const obj = ref({ a: 0, b: 0 });
const incrementA = () => obj.value.a++;
const reset = () => obj.value = { a: 0, b: 0 };

見てわかるように、オブジェクトのプロパティにアクセスするのにobj.value.aとするのはかなり冗長に思えてしまいます。 そこで使用するのがreactiveです。

import { reactive } from 'vue';

const obj = reactive({ a: 0, b: 0 });
const incrementA = () => obj.a++;
const reset = () => Object.assign(obj, { a: 0, b: 0 });

reactiverefと同じく変数をリアクティブにするものですが、オブジェクト専用になります。 refと違い、プロパティへのアクセスがobj.aのように直感的にできることがわかります。

しかしresetの実装を見るとわかるように、値そのものを変更する場合に直接代入ができず、Object.assign()を使用する必要があります。

基本的に、オブジェクトはreactive、それ以外はrefを使用するのが良いと思います。 これにはいろいろ意見があるようで、あくまで個人的な意見として記述しています。

computed

Options APIではcomputedプロパティとして定義していましたが、Vue 3では以下のようにcomputed()を使用します。

import { ref, computed } from 'vue';

const number = ref(1);
const doubleNumber = computed(() => number.value * 2);

const displayNumber = () => {
  console.log(number.value, doubleNumber.value);
}

computed()の第一引数に指定した関数がGetterとして動作します。 上記の例は単純で、numberを2倍にしたdoubleNumbercomputedで定義しています。 computedの値はref同様valueプロパティ経由でアクセスします。

Setterが必要な場合は、以下のようにgetsetをプロパティに持つオブジェクトを第一引数に指定します。

import { ref, computed } from 'vue';

const number = ref(1);
const doubleNumber = computed({
  get: () => number.value * 2,
  set: val => { number.value = val / 2; },
});

ライフサイクルフック

Composition APIでは、各ライフサイクルに該当する関数が用意されています。 以下がその対応表です。

Vue 2(Options API)Vue 3(Options API)Vue 3(Composition API)
beforeCreatebeforeCreate-
createdcreated-
beforeMountbeforeMountonBeforeMount()
mountedmountedonMounted()
beforeUpdatebeforeUpdateonBeforeUpdate()
updatedupdatedonUpdated()
beforeDestroybeforeUnmountonBeforeUnount()
destroyedunmountedonUnmounted()

使用する場合は、以下のように第一引数に実行したい処理(コールバック関数)を指定します。

import { ref, onMounted } from 'vue';

const number = ref(1);
onMounted(() => { number.value = 10 });

Composition APIでは、<script setup>直下に定義したすべての処理がbeforeCreatecreatedに該当します。 そのためこの2つは他と違って関数が用意されていません。

import { ref } from 'vue';

const number = ref(1);
number.value = 10; // これはcreatedの処理

当然のことだとは思いますが、通常のプログラム同様に事前に定義のないものへのアクセスはできません。 要は上から順番に処理されるということです。

import { ref } from 'vue';

number.value = 10;  // これはnumberが事前に定義されていないためNG
const number = ref(1);

またcreatedでAPIなどの非同期処理を行う際、以下のように直接awaitを指定することができます。

<script setup>
const { data } = await axios.get('/hoge');
</script>

props

コンポーネントのプロパティは、definePropsで定義します。

オプションなし
import { defineProps } from 'vue';

const props = defineProps(['hoge', 'fuga']);
オプションあり
import { defineProps } from 'vue';

const props = defineProps({
  hoge: {
    type: String,
    default: 'text'
  },
  fuga: {
    type: Number,
    required: true,
    validator: (v) => v > 0
  }
});

定義自体はOptions APIと変わりはないと思います。

Options APIでは、this.hogedataプロパティと同じアクセス方法でしたが、Composition APIでは設定した変数でアクセスします。 上記の場合はprops.hogeのような感じです。

emits

Vue 3から追加されたemitsは、defineEmitsで定義します。

検査なし
import { defineEmits } from 'vue';

const emit = defineEmits(['click']);
const onClick = () => { emit('click') };
検査あり
import { defineEmits } from 'vue';

const emit = defineEmits({
  click: (v) => v typeof === 'string'
});
const onClick = () => { emit('click') };

props同様、定義の方法自体はOptions APIと変わりありません。

Options APIではthis.$emit(click)で使用していましたが、Composition APIでは設定した変数を用いてemits('click')で使用します。

watch

Options APIではwatchプロパティとして定義していましたが、Composition APIではwatch()を使用します。 第一引数に監視対象のデータ、第二引数に処理(コールバック関数)を指定します。

import { ref, watch } from 'vue';

const number = ref(1);
watch(number, (cr, prev) => {
  console.log(cr, prev);
});

オブジェクトのプロパティを監視したい場合は、Getterとして監視対象を指定します。

import { reactive, watch } from 'vue';

const obj = reactive({ a: 1, b: 2 });
watch(() => obj.a, (cr, prev) => {
  console.log(cr, prev);
});

複数のデータを監視対象としたい場合は、以下のように配列を使用します。 この場合、どちらかの値が変化した場合に動作します。 引数にしている現在の値(cr)と変更前の値(prev)は、第一引数と同じ形式の配列が設定されます。

import { ref, watch } from 'vue';

const number1 = ref(1);
const number2 = ref(2);

watch([number1, number2], (cr, prev) => {
  console.log(cr, prev);  // number1 = 3 → cr: [3, 2], prev: [1, 2]
});

またwatch()StopHandlerを返すようになっており、これを実行することで監視を停止させることができます。

const stop = watch(number, (cr, prev) => { console.log(cr, prev)});
stop(); //監視の停止

watchEffect

Composition APIでは、watchとは別にwatchEffectという監視の仕組みがあります。

watchEffectでは、第一引数に実行する処理(コールバック関数)を指定するだけです。 watchとは異なり、監視対象を明示的に指定しません。

では何を監視するかというと、処理中に記述しているリアクティブなオブジェクトが勝手に監視されます。

import { ref, watchEffect } from 'vue';

const number = ref(1);
watchEffect(() => { console.log(number.value) });

上記はnumberが監視されることになります。 処理内に複数指定すれば、そのいずれかがの値が変化したときに処理されることになります。

watchEffectwatch同様にStopHandlerを返すようになっています。

const stop = watchEffect(() => { console.log(number.value) });
stop(); //監視の停止

一見便利に見えるwatchEffectですが、watchのみ以下のことが可能であると公式ドキュメントに記載があります。

  • 作用の効率的な実行
  • ウォッチャの再実行条件の明文化
  • ウォッチャされている状態に対しての、変更前後の値両方へのアクセス

これらのことを踏まえ、watchwatchEffectのどちらを使うか検討してみてください。

テンプレート参照($refs)

Options APIでは、ref属性を付与した要素をthis.$refsで取得することができました。

Options API
<template>
  <div ref="elm">Hello World!!</div>
</template>

<script>
export default {
  mounted() {
    console.log(this.$refs.elm);
  }
}
</script>

Composition APIでは、リアクティブ変数の定義に使用したrefを使用します。

Composition API
<template>
  <div ref="elm">Hello World!!</div>
</template>

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

const elm = ref(null);
onMounted(() => { console.log(elm.value) });
</script>

少しややこしいですが、refで定義する変数の名前をref属性で指定した値と同じにすることで紐づきます。 値の設定は、初回レンダリング後に行われます。

このように、refは2つの役割を持つため、使用する際はしっかりと区別する必要があります。

コンポーネントの読み込み

Options APIでは、コンポーネントを読み込み、componentsに指定する必要がありました。

Options API
<template>
  <HelloWorld />
</template>

<script>
import HelloWorld from './HelloWorld';

export default {
  components: {
    HelloWorld
  }
};
</script>

Composition APIでは、コンポーネントの読み込みだけで使用することができます。 かなり手間を省くことができますね。

Composition API
<template>
  <HelloWorld />
</template>

<script setup>
import HelloWorld from './HelloWorld';
</script>

おわりに

この記事では、Options APIと比較しながらComposition APIの基本について紹介していきました。 まだ紹介できていないですが、watchEffectのようにComposition API独自の仕様がいくつかあります。

これについては、別の記事でまとめたいと考えています。