こんにちは。システム開発課の今門です。
今回は、Vue.jsのリアクティブシステムについて説明します。
特に、Composition APIにおいて、どのようにリアクティブな状態を実現していくかを説明します。
Vue.jsのリアクティブシステムとは
Vue.jsのアプリケーションでは、データに変化があった場合、即座に反応してデータの同期/自動実行/DOMの編集などを行えるよう、つねに監視しています。この仕組みをリアクティブシステムといいます。
また、リアクティブシステム配下で監視されているデータをリアクティブデータといい、その時このデータはリアクティブな状態にあるといいます。
リアクティブデータの扱い
Options APIの場合
Options APIの場合、dataオプションにデータを登録すると、そのデータはリアクティブな状態になります。
export default { data() { return { count: 0 } }, methods: { increment() { this.count++ } } }
DOM側では次のように記述して、リアクティブデータを反映させます。
<button @click="increment">{{ count }}</button>
Composition APIの場合
Composition API でリアクティブシステムを実現するためには、reactive関数あるいはref関数を使って、データをリアクティブな状態にする必要があります。
reactive関数あるいはref関数を利用するには、vueからそれぞれ関数をimportします。
// reactive関数を使う場合 import { reactive } from 'vue' // ref関数を使う場合 import { ref } from 'vue'
リアクティブデータをDOMに反映させるには、setup関数で宣言し、それを返します。
import { reactive } from 'vue' export default { // setup関数は、Composition API 専用の特別なフックです。 setup() { // 状態を変化させる (処理A) // 状態をDOMに反映させる return { (処理Aの結果) } } }
setup関数内への記述は冗長になりがちなのですが、単一ファイルコンポーネントを利用している(*.vueファイルごとにコンポーネントを管理している)場合は、<script setup>を使い、シンプルで見通しのよい記載が可能になります。
<script setup> import { reactive } from 'vue' // 状態を変化させる (処理A) </script>
以上が、Vue.jsでのリアクティブデータの扱いになります。
Compositon APIでリアクティブな状態を実現するために使用するreactive関数、ref関数にはそれぞれ特徴があり、状況に応じて使い分けることが必要です。
まずはreactive関数について説明します。
reactive関数
reactive関数の特徴
reactive関数でリアクティブな状態を実現するには、次のように記述します。
<script setup> import { reactive } from 'vue' const state = reactive({ count: 0 }) function increment() { state.count++ } </script> <template> <button @click="increment"> {{ state.count }} </button> </template>
reactive関数の特徴は次のとおりです。
- 引数にオブジェクトをとる。
- ネストしたオブジェクトや配列を変化させた場合でも、リアクティブな状態を実現する。
- reactive関数の戻り値は、元のオブジェクトのプロキシであり、元のオブジェクトと等しくない。
- プロキシだけがリアクティブとなり、元のオブジェクトを変化させても、リアクティブにはならない。
reactive関数の制限
reactive関数にはふたつの制限があります。
- オブジェクトや配列などのオブジェクト型にのみ機能します。文字列や数値などのプリミティブ型には機能しません。
- リアクティブなオブジェクトの参照を常に同じに保つ必要があります。次のような操作により、リアクティブな繋がりは切れてしまいます。
リアクティブなオブジェクトの置き換え
let state = reactive({ count: 0 }) // 上記の参照({ count: 0 })は追跡されない state = reactive({ count: 1 })
オブジェクトのプロパティをローカル変数へ代入する
// n にstate.count を代入する場合、state.countの変更を追跡できない let n = state.count
オブジェクトのプロパティを関数に渡す
// この関数が受け取る平文番号とstate.count の変更を追跡することができない
callSomeFunction(state.count)
これらの制限に対処するためには、ref関数を使います。
ref関数
ref関数の特徴
ref関数は引数を受け取り、それを .value プロパティを持つ ref オブジェクトにラップして返します。
<script setup> import { ref } from 'vue' const count = ref(0) function increment() { // valueプロパティにアクセスする count.value++ } </script> <template> <button @click="increment"> // .valueの形をとらなくてもよい {{ count }} </button> </template>
ref関数の特徴は次のとおりです。
- reactive関数がオブジェクト型のみに機能するのに対し、文字列や数値などのプリミティブ型を扱える。
- reactive関数と異なり、ローカル変数に代入したり関数に渡したりしても、リアクティブな繋がりは切れない。
- ref関数で定義した変数は、 .value プロパティのみを持つ オブジェクトでラップされる。
- scriptタグ内で、ref関数で定義した変数にアクセスする場合、変数.valueの形をとる。
- template内で、ref関数で定義した変数にアクセスする場合、変数.valueの形をとらなくてもよい。これをアンラップという。
.valueのアンラップ
ref関数で .value を使わなければならないのは、JavaScript の言語的な制約によるものであるため、.valueの使用を好まないケースもあるかと思います。
.valueを使わなくてすむ(アンラップする)方法をいくつか紹介します。
リアクティブなオブジェクトとしてrefを扱う
リアクティブなオブジェクトのプロパティとして ref にアクセスしたり変化させたりすると自動的にアンラップされ、通常のプロパティと同じように振る舞うことができます。
const count = ref(0) const state = reactive({ count }) console.log(state.count) // 0 state.count = 1 console.log(count.value) // 1
コンポーネントインスタンス $refを使用する
$refを使用すると、コンパイル後のJavaScriptコードに.valueを追加してくれるため、Vueコンポーネント上で.valueを使う必要はありません。
$refを使うには、vite.config.jsで、pluginsのvueの引数にreactivityTransform:trueを設定する必要があります。
import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue({ reactivityTransform: true })] })
その結果、次のようにシンプルに記載することができます。
何らかの関数をimportする必要もありません。
<script setup> let count = $ref(0) function increment() { // .value は不要 count++ } </script> <template> <button @click="increment">{{ count }}</button> </template>
まとめ
Options APIでのリアクティブ化がdataオプション配下に変数を置くだけでよかったのと比較して、Composition APIの場合は、状況に応じたさまざまな記載方法が存在します。
一見、それらを理解するのは大変そうに見えますが、いったん理解してしまえば、Vueのリアクティブシステムをコントロールできるようになります。
VueやReactを始めとしたフロントのフレームワークにとって、リアクティブシステムを備えていることこそがそれらを使用する大きな理由となりますので、ぜひ理解したいところです。
[参考サイト]