5.1 ウォッチャー
スプリクトブロックで使用できるさまざまな機能
5.1.1 算出プロパティの役割
なぜウォッチャーが必要なのかを体感するための復習。
Mapに格納されているオブジェクトから1秒ごとに乱数を使ってデータを一つ抽出し表示するプログラム。
<script setup lang="ts">
import { ref, computed } from 'vue'
// カクテルリストデータを用意
const cocktailDataListInit = new Map<number, Cocktail>()
cocktailDataListInit.set(1, { id: 1, name: 'ホワイトレディ', price: 1200 })
cocktailDataListInit.set(2, { id: 2, name: 'ブルーハワイ', price: 1500 })
cocktailDataListInit.set(3, { id: 3, name: 'ニューヨーク', price: 1100 })
cocktailDataListInit.set(4, { id: 4, name: 'マティーニ', price: 1500 })
// カクテル番号のテンプレート変数を用意
const cocktailNo = ref(1)
// カクテル番号に対応するカクテル情報の算出プロパティを用意
const priceMsg = computed((): string => {
// カクテル番号に該当するカクテルデータを取得
const cocktail = cocktailDataListInit.get(cocktailNo.value)
// カクテル番号に該当する情報がない場合のメッセージを用意
let msg = '該当カクテルはありません。'
// カクテル番号に該当する情報があるなら
if (cocktail != undefined) {
// カクテル番号に該当するカクテルの名前と金額を表示する文字列を生成
msg = `該当するカクテルは${cocktail.name}で、価格は${cocktail.price}円です。`
}
// 表示文字列をリターン
return msg
})
// cocktailNoを1秒ごとに1~4の乱数を使って変更
setInterval((): void => {
cocktailNo.value = Math.round(Math.random() * 3) + 1
}, 1000)
interface Cocktail {
id: number
name: string
price: number
}
</script>
<template>
<p>現在のカクテル番号: {{ cocktailNo }}</p>
<p>{{ priceMsg }}</p>
</template>
setInterval() について
Vue では App.vue などのコンポーネントで用意したりリアクティブ変数の値が全く別の所、例えば別のコンポーネントなどから変更されることがある。
setInterval()を利用したのは、定期的に値を変化させること自体が目的ではなく、別のコンポーネントなどから変更される状況を疑似的に再現するためである。
1秒ごとにcocktalNoの値が変更するたびに算出プロパティ priceMsg が呼び出される。
リアクティブ変数として必要なデータを切り出したり加工したりする場合、算出プロパティは非常に便利である。
ただし、これはあくまで算出に使用するデータがスクリプトブロック内の変数である場合である。
仮に表示する情報をインターネットから取得する場合、算出関数内にインターネットアクセスのための非同期処理を記述することになる。
これは、スクリプトブロック内の変数からデータを切り出すのとは比べ物にならないくらい重いしょりである。
算出プロパティは、このような重い処理を行うには不向きな仕組みである為、このような場面での使用は推奨されていない。
5.1.2 リアクティブ変数の変化を監視する watchEffect()
算出プロパティの代わりに使用するのがウォッチャーである。
ウォッチャーはリアクティブ変数の値の変化に応じて特定の処理を実行するための仕組みである。
<script setup lang="ts">
import { ref, watchEffect } from 'vue'
// カクテル番号のテンプレート変数を用意
const cocktailNo = ref(1)
// カクテル番号に対応するカクテル情報のテンプレート変数を用意
const priceMsg = ref('')
// watchEffectを設定
watchEffect((): void => {
priceMsg.value = getCocktailInfo(cocktailNo.value)
})
// cocktailNoを1秒ごとに1~4の乱数を使って変更
setInterval((): void => {
cocktailNo.value = Math.round(Math.random() * 3) + 1
}, 1000)
interface Cocktail {
id: number
name: string
price: number
}
// カクテル番号に対応するカクテル情報を取得する関数
function getCocktailInfo(cocktailNo: number): string {
const cocktailDataListInit = new Map<number, Cocktail>()
cocktailDataListInit.set(1, { id: 1, name: 'ホワイトレディ', price: 1200 })
cocktailDataListInit.set(2, { id: 2, name: 'ブルーハワイ', price: 1500 })
cocktailDataListInit.set(3, { id: 3, name: 'ニューヨーク', price: 1100 })
cocktailDataListInit.set(4, { id: 4, name: 'マティーニ', price: 1500 })
// カクテル番号に該当するカクテルデータを取得
const cocktail = cocktailDataListInit.get(cocktailNo)
// カクテル番号に該当する情報がない場合のメッセージを用意
let msg = '該当カクテルはありません。'
// カクテル番号に該当する情報があるなら
if (cocktail != undefined) {
// カクテル番号に該当するカクテルの名前と金額を表示する文字列を生成
msg = `該当するカクテルは${cocktail.name}で、価格は${cocktail.price}円です。`
}
// 表示文字列をリターン
return msg
}
</script>
<template>
<p>現在のカクテル番号: {{ cocktailNo }}</p>
<p>{{ priceMsg }}</p>
</template>
今回は、1秒ごとにcocktailNoの値が変化するたびにインターネットにアクセス、カクテルデータを取得、その価格を表示することを想定している。
表示の更新はリアクティブ変数 priceMsg にデータを格納することで自動的に行われる。
リストでは実際にインターネットにアクセスしてデータを取得するのではなくカクテル情報を取得する関数 getCoktailInfo()を用意して簡易的に記述している。
これらの処理をウォッチャーとして設定しているのが watchEffext() 関数である。
watchEffect(
(): void => {
リアクティブ変数に応じて実行される処理
}
}
watchEffect() 関数は引数としてコールバック関数を受け取る。
コールバック関数の内部にはリアクティブ変数が変化したときに実行する処理を記述する。
Vueはこのコールバック関数内で利用されているリアクティブ変数を全て監視しており、それらのどれかひとつでも値が変更されるとコールバック関数を実行する。
5.1.2 監視対象を明示する watch()
Vue には watchEffect() 関数以外に監視対象の変数を指定してウォッチャーを登録する関数 watch() も存在する。
<script setup lang="ts">
import { ref, watch } from 'vue'
// カクテル番号のテンプレート変数を用意
const cocktailNo = ref(1)
// カクテル番号に対応するカクテル情報のテンプレート変数を用意
const priceMsg = ref('')
// watchを設定
watch(
cocktailNo,
(): void => {
priceMsg.value = getCocktailInfo(cocktailNo.value)
}
)
// cocktailNoを1秒ごとに1~4の乱数を使って変更
setInterval((): void => {
cocktailNo.value = Math.round(Math.random() * 3) + 1
}, 1000)
interface Cocktail {
id: number
name: string
price: number
}
// カクテル番号に対応するカクテル情報を取得する関数
function getCocktailInfo(cocktailNo: number): string {
const cocktailDataListInit = new Map<number, Cocktail>()
cocktailDataListInit.set(1, { id: 1, name: 'ホワイトレディ', price: 1200 })
cocktailDataListInit.set(2, { id: 2, name: 'ブルーハワイ', price: 1500 })
cocktailDataListInit.set(3, { id: 3, name: 'ニューヨーク', price: 1100 })
cocktailDataListInit.set(4, { id: 4, name: 'マティーニ', price: 1500 })
// カクテル番号に該当するカクテルデータを取得
const cocktail = cocktailDataListInit.get(cocktailNo)
// カクテル番号に該当する情報がない場合のメッセージを用意
let msg = '該当カクテルはありません。'
// カクテル番号に該当する情報があるなら
if (cocktail != undefined) {
// カクテル番号に該当するカクテルの名前と金額を表示する文字列を生成
msg = `該当するカクテルは${cocktail.name}で、価格は${cocktail.price}円です。`
}
// 表示文字列をリターン
return msg
}
</script>
<template>
<p>現在のカクテル番号: {{ cocktailNo }}</p>
<p>{{ priceMsg }}</p>
</template>
watch(監視対象リアクティブ変数,
(): vooid => {
監視対象が変化した際に実行される処理
}
}
watch() 関数は watchEffect() 関数と違い初回起動時にコールバック関数を実行しない。
そのため初回起動時のテンプレート変数 priceMsg の値は空文字のままとなり表示されない。
watch() による複数変数の監視
第1引数を配列とすれば複数の変数を監視対象にできる。
watch([selectedId, inputName],
(): void => {
// 必要な処理
}
}
5.1.4 即時実行の watch()
watch() でも初回起動時にコールバック関数を実行(即時実行)する方法がある。
これには、第3引数に {immediate: true} オプションを指定する。
watch(監視対象リアクティブ変数,
(): vooid => {
監視対象が変化した際に実行される処理
},
{immediate: true}
}
5.1.5 watch() における変更後の値に利用
watch() のメリットは、監視対象変数の値が変化した際、新旧それぞれの値を引数として受け取れること。
<script setup lang="ts">
import { ref, watch } from 'vue'
// カクテル番号のテンプレート変数を用意
const cocktailNo = ref(1)
// カクテル番号に対応するカクテル情報のテンプレート変数を用意
const priceMsg = ref('')
// watchを設定
watch(
cocktailNo,
(newVal, oldVal): void => {
// 表示用文字列を用意
let msg = '前のカクテル:'
msg += getCocktailInfo(oldVal)
msg += '<BR>現在のカクテル:'
msg += getCocktailInfo(newVal)
// 表示文字列をpriceMsgに設定
priceMsg.value = msg
}
)
// cocktailNoを1秒ごとに1~4の乱数を使って変更
setInterval((): void => {
cocktailNo.value = Math.round(Math.random() * 3) + 1
}, 1000)
interface Cocktail {
id: number
name: string
price: number
}
// カクテル番号に対応するカクテル情報を取得する関数
function getCocktailInfo(cocktailNo: number): string {
console.log('->', cocktailNo)
const cocktailDataListInit = new Map<number, Cocktail>()
cocktailDataListInit.set(1, { id: 1, name: 'ホワイトレディ', price: 1200 })
cocktailDataListInit.set(2, { id: 2, name: 'ブルーハワイ', price: 1500 })
cocktailDataListInit.set(3, { id: 3, name: 'ニューヨーク', price: 1100 })
cocktailDataListInit.set(4, { id: 4, name: 'マティーニ', price: 1500 })
// カクテル番号に該当するカクテルデータを取得
const cocktail = cocktailDataListInit.get(cocktailNo)
// カクテル番号に該当する情報がない場合のメッセージを用意
let msg = '該当カクテルはありません。'
// カクテル番号に該当する情報があるなら
if (cocktail != undefined) {
// カクテル番号に該当するカクテルの名前と金額を表示する文字列を生成
msg = `該当するカクテルは${cocktail.name}で、価格は${cocktail.price}円です。`
}
// 表示文字列をリターン
return msg
}
</script>
<template>
<p>現在のカクテル番号: {{ cocktailNo }}</p>
<!-- p>{{ priceMsg }}</p -->
<p v-html="priceMsg"></p>
</template>
watch(監視対象リアクティブ変数,
(newVal: データ型, oldVal: データ型): void => { // コールバックの引数を指定
監視対象が変化した際に実行される処理
}
}
複数変数監視の場合、newVal, oldValは配列になる。
5.2 ライフサイクルフック
5.2.1 ライフサイクルとは
App コンポーネントには読み込まれる段階、レンダリングされる段階など様々な状態がある。
このようなコンポーネントを解析、表示、非表示する間に生じるさまざまなコンポーネントの状態の遷移をライフサイクルという。
ライフサイクルのそれぞれの状態に応じて処理を行うライフサイクルフックという関数が用意されている。
5.2.2 ライフサイクルフックの具体例
<script setup lang="ts">
import {
ref,
computed,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onRenderTracked,
onRenderTriggered,
} from 'vue'
import type { DebuggerEvent } from 'vue' // データ型定義そのものをインポート
const heightInit = Math.round(Math.random() * 10)
const widthInit = Math.round(Math.random() * 10)
const height = ref(heightInit) // 縦
const width = ref(widthInit) // 横
const area = computed((): number => { // 算出プロパティ
return height.value * width.value
})
const change = (): void => { // イベントハンドラ
height.value = Math.round(Math.random() * 10)
width.value = Math.round(Math.random() * 10)
}
//------------------
// ライフサイクルフック
//------------------
onBeforeMount((): void => {
console.log(`beforeMount called: ${height.value} * ${width.value}`)
})
onMounted((): void => {
console.log(`mounted called: ${height.value} * ${width.value}`)
})
onBeforeUpdate((): void => {
console.log(`befpreUpdate called: ${height.value} * ${width.value}`)
})
onUpdated((): void => {
console.log(`updated called: ${height.value} * ${width.value}`)
})
onRenderTracked((event: DebuggerEvent): void => {
console.log(`renderTracked called: ${height.value} * ${width.value}`)
console.log(event)
})
onRenderTriggered((event: DebuggerEvent): void => {
console.log(`renderTriggered called: ${height.value} * ${width.value}`)
console.log(event)
})
</script>
<template>
<p>縦が{{ height }}、横が{{ width }}の長方形の面積は{{ area }}</p>
<button v-on:click="change">値を変更</button>
</template>
画面表示しただけでもいくつかのライフサイクルフックが実行される。
さらに[値を変更]ボタンをクリックするとさらにいくつかのライフサイクルフックが実行される。
5.2.3 Vue のライフサイクル
- Vueアプリケーションが起動する
- Vueアプリケーションの初期化処理が行われる
この段階でスクリプトブロック内のコードが実行され、テンプレート変数や算出プロパティ、メソッドなどが準備される - コンポーネントの解析処理が行われる
これによりテンプレートブロック内のコードが解析され、タグ構成(DOM構造)が決定される - 決定したDOM構造がレンダリングされる
- レンダリング処理が完了すると、表示状態になる
この状態で初めて画面が見えるようになる
この状態を公式ドキュメントでは Mounted と表現している - リアクティブシステムにより再レンダリングが必要になった際に行われる処理
再レンダリングが終わると再びMounted状態に戻る - 表示が不要になったコンポーネントに非表示処理を行う
- 非表示処理が完了すると、コンポーネントは非表示状態となる
この状態を公式ドキュメントでは Unmounted と表現している
5.2.4 Vue のライフサイクルフック
各処理/状態には前後に処理を挟み込めるようにライフサイクルフックが用意されている。
例えば、④のレンダリング処理の前には beforMount のフックが呼び出され、レンダリングが終了すれば mounted フックが呼び出される。
それぞれのフックが呼び出されるタイミングで処理を行うには、それぞれのフックにその処理を行うコールバック関数を登録して置けば良い。
onBeforMount((): void => { 処理 })
onMounted((): void => { 処理 })
5.2.5 デバッグ用のライフサイクルフック
renderTracked
renderTracked は該当するリアクティブ変数への初回アクセスのタイミングで実行される。
リアクティブ変数が3個用意されていれば、この3個の変数がそれぞれアクセスのたびに renderTracked が呼び出される。
これはデバッグでの使用を想定した仕様で、そのためコールバック関数には引数として DebuggerEvent オブジェクトが渡される。
onRenderTracked((event: DebuggerEvent): void => {
console.log(`renderTracked called: ${height.value} * ${width.value}`)
console.log(event)
})
{effect: ReactiveEffect, target: RefImpl, type: 'get', key: 'value'}
{effect: ReactiveEffect, target: RefImpl, type: 'get', key: 'value'}
{effect: ReactiveEffect, target: ComputedRefImpl, type: 'get', key: 'value'}
target: RefImpl は ref()によるリアクティブ変数
target: ComputedRefImpl は computed() によるリアクティブ変数
renderTriggered
[値を変更]ボタンをクリックすると下記のメソッド内の処理によってリアクティブ変数の値が変更される。
const change = (): void => {
height.value = Math.round(Math.random() * 10)
width.value = Math.round(Math.random() * 10)
}
リアクティブ変数が変更されるとリアクティブシステムはこれを検知して画面の再描画を行う。
これは「6.リアクティブシステムによる再レンダリング処理」で、処理の前に beforUpdate フックが、処理の後に update フックが呼び出される。
その beforUpdate の直前には renderTriggered フックが3回(リアクティブ変数の数だけ)呼び出される。
これは「10.再レンダリングの際にリアクティブ変数へのアクセス」処理で、再レンダリングに先立ってリアクティブ変数の値の変化を探知した際に呼び出されるフックである。
renderTriggered もデバッグ用であり、コールバック関数の引数として DebuggerEvent オブジェクトを受け取る。
特徴的なのは、renderTracked の時の DevuggerEvent オブジェクトとは違い、newValue プロパティがあることで、これによってリアクティブ変数がどのような値に変化したのかが読み取れる。
beforUpdate called: 10 * 7
renderTriggered called: 10 * 7
{effect: ReactiveEffect, target: RefImpl, type: 'set', key: 'value', newValue: 10}
renderTriggered called: 10 * 7
{effect: ReactiveEffect, target: ComputedRefImpl, type: 'set', key: 'value', newValue: undefined}
renderTriggered called: 10 * 7
{effect: ReactiveEffect, target: RefImpl, type: 'set', key: 'value', newValue: 7}
updated called: 10 * 7
beforeUnmount と unmounted
beforeUnmount と unmounted のフックを設定するコードがないのは、App.vue は始点となるコンポーネントのため、App.vue そのものが Unmounted になるのではなくアプリケーションそのものが終了してしまうためである。
App.vue から他のコンポーネントを読み込むときに利用する。
7.2.6 ライフサイクルフックのまとめ
ライフサイクルフックをうまく活用することで、適切なタイミングで適切な処理を行えるようになる。
ライフサイクルフック名 | 呼び出しのタイミング |
---|---|
beforeCreate | 起動開始直後、Vue アプリケーションの初期化処理前 |
created | Vue アプリケーションの初期化処理後 |
beforeMount | コンポーネントの解析処理後、決定した DOM をレンダリングする直前 |
mounted | DOM のレンダリングが完了し、表示状態になった時点。Mounted状態になった時点。 |
beforeUpdate | リアクティブデータが変更され、DOM の再レンダリング処理を行う前 |
updated | DOM の再レンダリングが完了した時点 |
beforeUnmount | コンポーネントの DOM の非表示処理を開始する直前 |
unmounted | コンポーネントの DOM の非表示処理が完了した時点。Unmountedな状態になった時点 |
errorCaptured | 配下のコンポーネントを含めてエラーを検知した時点 |
renderTracked | リアクティブ変数に初めてアクセスが発生した時点 |
renderTriggered | リアクティブ変数が変更されたのを検知して、その変数へのアクセスがあった時点 |
activated | コンポーネントが待機状態ではなくなった時点 |
deactivated | コンポーネントが待機状態になった時点 |
beforeCreate と created について
「2.Vue アプリケーションの初期化処理」のタイミングでスクリプトブロックのコードが実行されるので、スクリプトブロック内のコードで beforeCreate と create の代わりにできるため、スクリプトブロックからこれらのフックを呼びだす必要はない。
これらのフックは Options API によってスクリプトブロックを記述するために存在している。
Unmonted からの再度表示
Unmonted の状態のコンポーネントを再度表示する場合は beforeMount から再開される。
5.3 script setup の本当の姿
5.3.1 defineComponent と setup
setup 属性がある場合のコードと無い場合のコード
<script setup lang="ts">
import { ref, computed } from 'vue'
// カクテルリストデータを用意
const cocktailDataListInit = new Map<number, Cocktail>()
cocktailDataListInit.set(1, { id: 1, name: 'ホワイトレディ', price: 1200 })
cocktailDataListInit.set(2, { id: 2, name: 'ブルーハワイ', price: 1500 })
cocktailDataListInit.set(3, { id: 3, name: 'ニューヨーク', price: 1100 })
cocktailDataListInit.set(4, { id: 4, name: 'マティーニ', price: 1500 })
// カクテル番号のテンプレート変数を用意
const cocktailNo = ref(1)
// カクテル番号に対応するカクテル情報の算出プロパティを用意
const priceMsg = computed((): string => {
// カクテル番号に該当するカクテルデータを取得
const cocktail = cocktailDataListInit.get(cocktailNo.value)
// カクテル番号に該当する情報がない場合のメッセージを用意
let msg = '該当カクテルはありません。'
// カクテル番号に該当する情報があるなら
if (cocktail != undefined) {
// カクテル番号に該当するカクテルの名前と金額を表示する文字列を生成
msg = `該当するカクテルは${cocktail.name}で、価格は${cocktail.price}円です。`
}
// 表示文字列をリターン
return msg
})
// cocktailNoを1秒ごとに1~4の乱数を使って変更
setInterval((): void => {
cocktailNo.value = Math.round(Math.random() * 3) + 1
}, 1000)
interface Cocktail {
id: number
name: string
price: number
}
</script>
<template>
<p>現在のカクテル番号: {{ cocktailNo }}</p>
<p>{{ priceMsg }}</p>
</template>
<script lang="ts">
import { defineComponent, ref, computed } from 'vue'
// このオブジェクトの定義方法は2種類
// Options APIとComposition API
// このサンプルはComposition API
export default defineComponent({
name: 'App', // コンポーネント名
// scriptブロックで記述していたコードはすべてsetup()の中に記述
setup() {
// カクテルリストデータを用意
const cocktailDataListInit = new Map<number, Cocktail>()
cocktailDataListInit.set(1, { id: 1, name: 'ホワイトレディ', price: 1200 })
cocktailDataListInit.set(2, { id: 2, name: 'ブルーハワイ', price: 1500 })
cocktailDataListInit.set(3, { id: 3, name: 'ニューヨーク', price: 1100 })
cocktailDataListInit.set(4, { id: 4, name: 'マティーニ', price: 1500 })
// カクテル番号のテンプレート変数を用意
const cocktailNo = ref(1)
// カクテル番号に対応するカクテル情報の算出プロパティを用意
const priceMsg = computed((): string => {
// カクテル番号に該当するカクテルデータを取得
const cocktail = cocktailDataListInit.get(cocktailNo.value)
// カクテル番号に該当する情報がない場合のメッセージを用意
let msg = '該当カクテルはありません。'
// カクテル番号に該当する情報があるなら
if (cocktail != undefined) {
// カクテル番号に該当するカクテルの名前と金額を表示する文字列を生成
msg = `該当するカクテルは${cocktail.name}で、価格は${cocktail.price}円です。`
}
// 表示文字列をリターン
return msg
})
// cocktailNoを1秒ごとに1~4の乱数を使って変更
setInterval((): void => {
cocktailNo.value = Math.round(Math.random() * 3) + 1
}, 1000)
// テンプレート変数として利用するものを戻り値としてリターンする。
return {
cocktailNo,
priceMsg,
}
},
})
interface Cocktail {
id: number
name: string
price: number
}
</script>
<template>
<p>現在のカクテル番号: {{ cocktailNo }}</p>
<p>{{ priceMsg }}</p>
</template>
defineComponent() 関数の実行結果を export している。
関数名の通りコンポーネントの処理内容が定義される。
引数にはコンポーネントの処理内容を表すオブジェクトを引き渡す。
このオブジェクトの定義方法は2種類あり、Option API と Composition API という名称がついていて、上記の両コードは Composition API によるものである。
defineComponent() の引数に指定するオブジェクトの記述方法
- name プロパティを定義
これがコンポーネント名を表す - setup() 関数
これまでスクリプトブロックに記述していたコードを setup() 関数を使用してその中に記述する。
setup() 関数の戻り値としてテンプレート変数として利用するものをリターンする。
setup 属性とは
script setup タグが登場したのは Vue 本体のバージョン 3.2 以降であり、それ以前は「defineComponent() と setup() を記述し、テンプレート変数を setup() の戻り値とする」という定型的なコードを記述する必要があった。
これらを簡易的に記述できるようにしたものが setup 属性である。
5.3.2 setup と reactive と toRefs
Vue にはオブジェクトをまとめてリアクティブデータに変換してくれる reactive() 関数がある。
この reactive() 関数を setup() 関数内で利用した場合、リアクティブ変数としてリターンする際にデータをバラバラにしてテンプレートブロックで利用できるようにする仕組みがある。
<script lang="ts">
import { defineComponent, reactive, computed, toRefs } from 'vue'
export default defineComponent({
name: 'app',
setup() {
// リアクティブなテンプレート変数をreactive()を使ってまとめて用意
const data = reactive({
PI: 3.14,
radius: Math.round(Math.random() * 10),
})
// 円の面積の算出プロパティを用意
const area = computed((): number => {
return data.radius * data.radius * data.PI
})
// 半径のテンプレート半数に新しい乱数を1秒ごとに格納
setInterval((): void => {
data.radius = Math.round(Math.random() * 10)
}, 1000)
// テンプレート変数をリターン
return {
data,
area,
}
},
})
</script>
<template>
<p>半径{{ data.radius }}の円の面積を円周率{{ data.PI }}で計算すると、{{ area }}</p>
</template>
<script lang="ts">
import { defineComponent, reactive, computed, toRefs } from 'vue'
export default defineComponent({
name: 'app',
setup() {
// リアクティブなテンプレート変数をreactive()を使ってまとめて用意
const data = reactive({
PI: 3.14,
radius: Math.round(Math.random() * 10),
})
// 円の面積の算出プロパティを用意
const area = computed((): number => {
return data.radius * data.radius * data.PI
})
// 半径のテンプレート半数に新しい乱数を1秒ごとに格納
setInterval((): void => {
data.radius = Math.round(Math.random() * 10)
}, 1000)
// テンプレート変数をリターン
return {
...toRefs(data),
area,
}
},
})
</script>
<template>
<p>半径{{ radius }}の円の面積を円周率{{ PI }}で計算すると、{{ area }}</p>
</template>
toRefs()
toRefs() 関数はリアクティブなオブジェクトを個別の参照(ref)に変換できる。
「…」スプレッド演算子
配列やオブジェクトの前に「…」が付けられているものはスプレッド構文とよばれるもので、連続する要素の内容をまとめて記述することのできる記述法である。
…toRefs(data)
reactive() 関数を使用して作成されたオブジェクトを toRef() 関数を使用せずに分割する場合、個別のプロパティはリアクティブではなくなる。
setup() 関数の戻り値をリターンする際にスプレッド演算子(…)と toRefs() 関数を組み合わせることで reactive() で用意したリアクティブなテンプレート変数オブジェクトを展開している。
結果、テンプレートブロックではプロパティ名ごとに別の変数として利用できるようになる。
ただし、この方法が有効なのは setup() 関数を利用した従来型の記述の場合のみである。
5.4 Options API
5.4.1 Optons API とは
setup 属性・setup() 関数を使わず別の記述法を使う伝統的な記述方法。
Composition API は Vue 3 の開発段階で Options API の欠点を改善するために生まれた。
5.4.2 Options API の基本構文
<script setup lang="ts">
import {ref, computed, onUpdated, watch} from 'vue'
// テンプレート変数
const heightInit = Math.round(Math.random() * 10)
const widthInit = Math.round(Math.random() * 10)
const height = ref(heightInit) // 縦
const width = ref(widthInit) // 横
// 算出プロパティ
const area = computed((): number => {
return height.value * width.value
})
// イベントハンドラ
const change = (): void => {
height.value = Math.round(Math.random() * 10)
width.value = Math.round(Math.random() * 10)
}
// ライフサイクルフック
onUpdated((): void => {
console.log(`updated called: ${height.value} * ${width.value}`)
})
// ウォッチャー
watch(height, (newVal: number, oldVal: number): void => {
console.log(`height newVal: ${newVal}, oldVal: ${oldVal}`)
})
watch(width, (newVal: number, oldVal: number): void => {
console.log(`width newVal: ${newVal}, oldVal: ${oldVal}`)
})
</script>
<template>
<p>縦が{{ height }}、横が{{ width }}の長方形の面積は{{ area }}</p>
<button v-on:click="change">値を変更</button>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'App',
// これまでテンプレート変数として記述してきたリアクティブな変数を用意
data() {
return {
height: Math.round(Math.random() * 10),
width: Math.round(Math.random() * 10),
}
},
// 算出プロパティ
// データプロパティへのアクセスはthis.プロパティ名
computed: {
area(): number {
return this.height * this.width
},
},
// メソッド
// データプロパティへのアクセスはthis.プロパティ名
methods: {
change(): void {
this.height = Math.round(Math.random() * 10)
this.width = Math.round(Math.random() * 10)
},
},
// ライフサイクルフック
updated(): void {
console.log(`updated called: ${this.height} * ${this.width}`)
},
// ウォッチャー
watch: {
height(newVal: number, oldVal: number): void {
console.log(`height newVal: ${newVal}, oldVal: ${oldVal}`)
},
width(newVal: number, oldVal: number): void {
console.log(`width newVal: ${newVal}, oldVal: ${oldVal}`)
},
},
})
</script>
<template>
<p>縦が{{ height }}、横が{{ width }}の長方形の面積は{{ area }}</p>
<button v-on:click="change">値を変更</button>
</template>
export default defineComponent({
name: "コンポーネント名", // (1)
data() { // (2)
return {
データプロパティ名:
:
}
},
computed: { // (3)
算出プロパティ名(): データ型 {
算出処理
},
:
},
methods: { // (4)
メソッド名(): void {
処理
},
:
}
})
- コンポーネント名
- データプロパティ
これまでテンプレート変数として記述してきたリアクティブ変数を用意 - 算出プロパティ定義
- メソッド定義
データプロパティへのアクセス方法
this.プロパティ名
5.4.3 Options API でのライフサイクルフック
export default defineComponent({
:
// ライフサイクルフック
updated(): void {
console.log(`updated called: ${this.height} * ${this.width}`)
}
})
単純にライフサイクルフック名の関数を定義し、その中に処理を記述する。
ライフサイクルフック名に on を付与する必要はない。
5.4.4 Options API でのウォッチャー
export default defineComponent({
:
// ウォッチャー
watch: {
height(newVal: number, oldVal: number): void {
console.log(`height newVal: ${newVal}, oldVal: ${oldVal}`)
},
width(newVal: number, oldVal: number): void {
console.log(`width newVal: ${newVal}, oldVal: ${oldVal}`)
},
},
})
watch パウロパティを設定し、関数名として監視対象データのプロパティを指定する。
this.プロパティ名
export default defineComponent({
:
watch: {
監視対象データプロパティ名(newVal: データ型, oldVal: データ型): void {
:
},
{immediate: true}
}
watchEffect
Composition API でウォッチャーを定義する関数には、watchEffect() と watch() のふたつがあったが、Options API には watch のみ存在しており watchEffect に当たるものは存在しない。
5.4.5 Composition API の利点
thisの問題
export default defineComponent({
data() {
return {
cocktailNo: 1, // (1)
priceMsg: ""
}
},
watch: {
cocktailNo(newVal: numbre, oldVal: numbre): void {
:
}
},
// 初期化処理
create(): void {
() => {
setInterval( // (2)
(): void => {
this.cocktailNo = Math.round(Math.random() * 3) + 1 // (3)
},
1000
)
}
}
))
初期化処理
setup スクリプトブロックはライフサイクル(2)の「アプリケーションの初期化処理」で実行されるので初期化処理に該当するようなコードは setup スプリプとブロック内に記述できた。
これが、Options API の場合はライフサイクルフックの 「beforeCreate」または「created」に記述しなければいけない。
初期化処理のうちデータプロパティへのアクセスを伴うものは created に記述する。
というのは、beforeCreate では Vue アプリケーションの初期化処理が行われておらずデータプロパティが存在しないためである。
上記コードはそのままではエラーになり、原因は(3)にある。
Options API ではデータプロパティが defineComponent() の引数オブジェクトの data() プロパティという形でまとまっているため create() プロパティ、computed プロパティ、methods プロパティなど他のプロパティからアクセスする場合はいったん this を介す必要がある。
ところが、(3)のように関数内関数で this を利用した場合、this は関数自身を表すことになりデータのプロパティを参照できなくなり、エラーとなる。
その他問題点
Composition APIに比べて各プロパティがバラバラに列挙されているため、this.cocktailNoが指すものがわかりにくくなる。
このコードの散財がOptions APIの最大の問題点である。短いコードではあまり問題にならないが、本運用アプリケーションのようにコードが増えてくるとコードの見通しが悪くなり再利用性も低下する。
それらを改善するために考え出されたのがComposition APIそしてその記述を簡略化できるのがsetup属性である。
コメント