【Vue 3】Composition API の基本
はじめに
※ Vue 2の知識があることを前提の記事となっています。
今回の題材である Composition API 導入により、Vueの<script>
部分の記述が大きく変わります。
Vue 2からVue 3への変更で一番大きなものは Composition API の導入と言っても過言ではないでしょう。
Composition APIは、Vue 2でも拡張機能として導入することはできましたが、多くのユーザーは基本的な Options API を使用していたはずです。
Options APIでは、オブジェクトプロパティとしてdata
やmethods
などの役割ごとにまとめて記述していました。
<script>
export default {
data: () => ({
count: 0,
})
methods: {
increment() {
this.count++;
},
decrement() {
this.count--;
}
}
}
</script>
これは Composition API を使用すると以下のように変わります。
<script setup>
import { ref } from 'vue';
const count = ref(0);
const increment = () => count.value++;
const decrement = () => count.value--;
</script>
見てわかるようにdata
やmethods
などの記述がなくなりスッキリした印象です。
この記述の変化により、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
はリアクティブではないのです。
変数をリアクティブとするためには次に説明するref
とreactive
を使用します。
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 });
reactive
もref
と同じく変数をリアクティブにするものですが、オブジェクト専用になります。
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倍にしたdoubleNumber
をcomputed
で定義しています。
computed
の値はref
同様value
プロパティ経由でアクセスします。
Setterが必要な場合は、以下のようにget
、set
をプロパティに持つオブジェクトを第一引数に指定します。
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) |
---|---|---|
beforeCreate | beforeCreate | - |
created | created | - |
beforeMount | beforeMount | onBeforeMount() |
mounted | mounted | onMounted() |
beforeUpdate | beforeUpdate | onBeforeUpdate() |
updated | updated | onUpdated() |
beforeDestroy | beforeUnmount | onBeforeUnount() |
destroyed | unmounted | onUnmounted() |
使用する場合は、以下のように第一引数に実行したい処理(コールバック関数)を指定します。
import { ref, onMounted } from 'vue';
const number = ref(1);
onMounted(() => { number.value = 10 });
Composition APIでは、<script setup>
直下に定義したすべての処理がbeforeCreate
とcreated
に該当します。
そのためこの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.hoge
とdata
プロパティと同じアクセス方法でしたが、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
が監視されることになります。
処理内に複数指定すれば、そのいずれかがの値が変化したときに処理されることになります。
watchEffect
もwatch
同様にStopHandler
を返すようになっています。
const stop = watchEffect(() => { console.log(number.value) });
stop(); //監視の停止
一見便利に見えるwatchEffect
ですが、watch
のみ以下のことが可能であると公式ドキュメントに記載があります。
- 作用の効率的な実行
- ウォッチャの再実行条件の明文化
- ウォッチャされている状態に対しての、変更前後の値両方へのアクセス
これらのことを踏まえ、watch
とwatchEffect
のどちらを使うか検討してみてください。
テンプレート参照($refs)
Options APIでは、ref
属性を付与した要素をthis.$refs
で取得することができました。
<template>
<div ref="elm">Hello World!!</div>
</template>
<script>
export default {
mounted() {
console.log(this.$refs.elm);
}
}
</script>
Composition APIでは、リアクティブ変数の定義に使用したref
を使用します。
<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
に指定する必要がありました。
<template>
<HelloWorld />
</template>
<script>
import HelloWorld from './HelloWorld';
export default {
components: {
HelloWorld
}
};
</script>
Composition APIでは、コンポーネントの読み込みだけで使用することができます。 かなり手間を省くことができますね。
<template>
<HelloWorld />
</template>
<script setup>
import HelloWorld from './HelloWorld';
</script>
おわりに
この記事では、Options APIと比較しながらComposition APIの基本について紹介していきました。
まだ紹介できていないですが、watchEffect
のようにComposition API独自の仕様がいくつかあります。
これについては、別の記事でまとめたいと考えています。