Lesson 8
状態管理
目次
Lesson 8
Chapter 1
状態管理について
1. 状態管理とは
Web アプリケーションでは、様々なデータを使用して HTML ページを表示します。
サーバから取得したデータで画面表示を行うのみでなく、ユーザーが入力した内容や、ログイン/ログアウトの状態によって表示内容を切り替えたりすることも必要になります。
これらのデータが複数のコンポーネントで共通に使用される場合には、データ状態を共有しつつ、相互に矛盾なく更新/保持する必要が生じます。
このようなアプリケーションの保持するデータの管理を「状態管理」と呼んでいます。
フロントエンド開発においては、この状態管理を適切に行うことが重要となります。
2. 状態管理の方法
2-1. コンポーネント間のデータの受渡し
Lesson 6 で学習しましたように、Vue には、コンポーネント間でデータの受渡しを行う方法がいくつか用意されています(例えば次のようなもの)。
No | 機能 | 簡単な説明 |
---|---|---|
1 | props | 親コンポーネントから子コンポーネントに値を渡す。 |
2 | emit | 子コンポーネントから親コンポーネントにイベントを渡す。 |
3 | 状態管理機能 | Pinia や vuex などのライブラリでコンポーネント間でデータを共有。 |
4 | provide / inject | 親コンポーネントから子コンポーネントにデータやロジックを渡す。 |
規模の小さなアプリケーションの場合は、props や emit だけで構成するという選択肢もありますが、規模の大きなアプリケーションの場合は、保有する状態の数やその組合せも多くなるため、コンポーネント内で個別にデータを管理するのは困難となります。
2-2. 状態管理ライブラリ
そこで、中規模以上の開発では「状態管理ライブラリ」というものを使用して、コンポーネントから独立してグローバルに状態(データ)を管理することが一般的です。
Vue で使用できる状態管理ライブラリとして「Vuex(ビューエックス)」と「Pinia(ピニア)」 があります。
元々は、Vuex が Vue の公式ライブラリでしたが、現在はその後継(※)となる Pinia が Vue の公式ライブラリとなっているため、このカリキュラムでは Pinia を使用して学習を進めていきます。
Pinia と Vuex 5
Pinia は「Composition API に対応する状態管理ストアを再設計する実験」として 2019 年に開発が始まりました。
一方、Vuex は、従来の設計を大幅に見直す新バージョン 5(Vuex 5)の開発を進めていましたが、Pinia の開発者も Vuex 5 のコアチームのメンバーに所属しており、Vuex 5 と Pinia は相互に影響を受けつつ開発が進められました。
結果として Pinia には、Vuex 5 とほぼ同じか拡張された API が実装されることになり、最終的に Vue の公式ライブラリとして採用されました(Vuex 5 はリリースされませんでした)。
次のように、Vuex の公式ページにおいては「Pinia の使用を強く推奨」するとともに「Pinia は Vuex 5 の別名と考えてよい」と紹介されています。
3. Pinia について
3-1. Pinia の公式ページ
先に触れましたとおり、Pinia(ピニア)は Vue 公式の状態管理ライブラリです。
Pinia には、以下のような公式ページが用意されています。
3-2. Pinia の機能
Pinia がどういう機能を提供するかを簡単に説明しておきます。
下図のように、Pinia はコンポーネント間の共通データを「State」(※)に保持します。
この State は、外部から書き換えることはできず「Actions」に定義されたメソッドでのみ書き換えることができます。
また、State を外部から取得するためには「Getters」というメソッドを使用します。
以上のような State、Actions、Getters を提供する機能群を一般に「Store(ストア)」といい、Pinia は、この Store の部分を提供するライブラリとなります。
State Management
状態管理とは、英語の「State Management」を日本語に訳したものです。
State(状態)はアプリケーションの保持しているデータを指し、これを Management(管理)するために、Pinia などのライブラリを使用することになります。

Lesson 8
Chapter 2
Pinia の導入
1. Pinia のインストール
1-1. インストール方法の確認
それでは、Vite プロジェクトに Pinia をインストールしていきます。
インストール方法は、公式ページに記載されています。
npm を使用する場合のコマンドは、次のところです。
npm install pinia
1-2. 既存のプロジェクトにインストールする
ここでは、Lesson 7 で作成した vite-sample2
プロジェクトにコードを追加していきます。
ターミナルを開いて、次のように npm install pinia
というコマンドを実行しましょう。
インストールが成功すると package.json
に "pinia": "^2.XX.XX"
のように、pinia のライブラリが追加されているはずです。
インストール自体はこれで完了です。
以下、初期設定に必要なコードを追加していきます。
2. Pinia の設定と実装
プロジェクトで Pinia が使用できるようにコードを追加していきます。
2-1. Pinia をプラグインとして追加する
Pinia のインスタンスを Vue のプラグインとして追加する方法は、公式サイトで以下のように紹介されています。
Vue Router のときと同様に、プラグインの追加には use()
関数を使用します。
上の例にならって、main.js
ファイルを以下のように修正します。
多少書き方は異なりますが、指定している内容は全く同じです。
全てのコードをテキストで表示
src\main.js
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router/index'
import { createPinia } from 'pinia'
const pinia = createPinia()
createApp(App)
.use(router)
.use(pinia)
.mount('#app')
以上で、プロジェクト内で Pinia が使用できるようになりました。
続いて、Store を定義して、コンポーネントから操作できるようにしていきます。
2-2. Store を作成する
① 公式の記載例
まず、Store を 1 つ作成します。ここでは、以下の公式ページに紹介されている Store を参考にして Store の定義を行います。
この例は、シンプルではありますが、state、getters、actions の 3 つの基本機能が定義されています。なお、見出しに「Option Store」とあるように、Options API と同じような書き方になっています。
② コードの記載
それでは、実際にコードを書いていきましょう。
次のように、src
ディレクトリの下に stores
ディレクトリを追加した上で、counter.js
ファイルを追加してください。
counter.js
ファイルに記述したコードは次のとおりです。
state:
の書き方のみ、公式の例から少し変更しています(もちろん、公式のとおりに書いても OK です)。
src\stores\counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => {
return {
count: 0,
name: 'Eduardo'
}
},
getters: {
doubleCount: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
以下、記載している内容について見ていきます。
③ 記載したコードの確認
Store は、defineStore 関数を使用して定義します。
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', { オプションを指定 })
defineStore 関数の第 1 引数には、この Store を識別する Id を記載します。
Id 名は何でも良いのですが、ファイル名と一致させるのが一般的です(ここでは 'counter'
と定義)。
defineStore 関数の第 2 引数にはオプション(options
)を定義します。
定義できるオプションは、次のとおりです。
No | option | 説明 |
---|---|---|
1 | state | 保持する共通データを定義する(リアクティブ変数に相当) |
2 | getters | state からデータを取得するための getter を定義する(算出プロパティ computed に相当) |
3 | actions | state に保存されているデータを更新するメソッドを定義する(メソッドに相当) |
・state の定義
state には、以下のような形でリアクティブな変数を定義します。
ここでは、count
と name
という 2 つの変数を定義しています。
state: () => {
return {
count: 0,
name: 'Eduardo'
}
},
・getters の定義
getters には Store のデータを取得するための getter 関数を定義します。
この getter 関数の第 1 引数で、state
を受け取ります。
ここでは、state.count * 2
として、count
変数を 2 倍した値を戻り値としています。
getters: {
doubleCount: (state) => state.count * 2,
},
・actions の定義
actions には、state の値を更新するためのメソッドなどを記入します。
ここでは、count
変数に 1
を加算する increment
メソッドを定義しています。
actions: {
increment() {
this.count++
},
},
2-3. コンポーネントで Store を利用する
次に、作成した Store をコンポーネント側で呼び出して使用するようにします。
① 公式の記載例
setup() 関数から Store を呼び出す方法は、以下の公式ページに紹介されています。
Store の定義ファイル counter.js
から useCounterStore
をインポートして、setup() 構文内でインスタンス化しています(const store = useCounterStore()
のところ)。
このインスタンス store
を使用して、counter
ストアの操作を行うことになります。
② コードの記載
それでは、手元のプロジェクトにコードを追加していきます。
src\components\Home.vue
コンポーネントを次のように修正してください(赤枠部分)。
修正後の Home.vue
コンポーネントは次のとおりです。
src\components\Home.vue
<script setup>
import { useCounterStore } from '../stores/counter'
const store = useCounterStore()
</script>
<template>
<div>Home</div>
<button @click="store.increment">加算</button>
<div>name: {{ store.name }}</div>
<div>count: {{ store.count }}</div>
<div>doubleCount: {{ store.doubleCount }}</div>
</template>
以下、記載している内容について見ていきます。
③ 記載したコードの確認
次のところで、counter.js
ストアから useCounterStore
をインポートして、store
という名称のインスタンスを生成しています(名称は何でも構いません)。
import { useCounterStore } from '../stores/counter'
const store = useCounterStore()
・state の値を取得
次のところで、Store 内で定義したリアクティブ変数 name
と count
を表示しています。
<div>name: {{ store.name }}</div>
<div>count: {{ store.count }}</div>
単純に、store.リアクティブ変数名
という形式で取得できます。
・getters の値を取得
getters で定義した doubleCount
も、state と同様の形式で取得・表示できます。
<div>doubleCount: {{ store.doubleCount }}</div>
・actions を実行
Store で定義した actions(increment
)も、以下のように、store.アクション名
という形式で使用することができます。
<button @click="store.increment">加算</button>
2-4. ブラウザで動作確認
それでは、VSCode のターミナルを開き、npm run dev
コマンドでローカルサーバを立ち上げます。
ブラウザから http://localhost:5173/ にアクセスして画面を表示してください(ローカルサーバを複数立ち上げている場合などは URL が異なる場合があります)。
下図の「加算」ボタンは、actions(increment
)を実行するものです。
name と count は、ストアの state を表示する部分で、それぞれ初期値が表示されています。
doubleCount には、getters(doubleCount
)の値が標示されます。
それでは「加算」ボタンをクリックしてみましょう。
クリックする度に、値がリアクティブに更新されます。
以下は、3 回クリックした場合の画面です。
以上で、pinia の導入は完了です。
次のチャプターから、pinia の使用方法について詳しく見ていきます。

Lesson 8
Chapter 3
Store(ストア)の定義
このチャプターでは、Store(ストア)の定義方法について見ていきます。
1. 構文(Option Stores と Setup Stores)
Store の記述方法は「Option Stores」と「Setup Stores」の 2 つの形式から選択することができます。
① Option Stores
先ほどは、以下のような「Option Stores」形式を使用しました。
state:
、getters:
、actions:
という 3 つのオプションを個別に定義しています。
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0, name: 'Eduardo' }),
getters: {
doubleCount: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
② Setup Stores
もう一つの構文として「Setup Stores」というものがあります。
これは、次のように、setup() 構文と同じような書き方となります。
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const name = ref('Eduardo')
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, name, doubleCount, increment }
})
下表のとおり、state
は ref 関数で定義し、getters
は computed(算出プロパティ)で定義し、actions
はメソッドとして定義します。
No | Option Stores | Setup Stores |
---|---|---|
1 | state に定義 | ref 関数を使用して定義 |
2 | getters に定義 | 算出プロパティ computed を使用して定義 |
3 | actions に定義 | function(メソッド)で定義 |
Option Stores と Setup Stores のどちらを使用するか
Pinia 公式では、Option Stores と Setup Stores は「使いやすい方」を選択すればよく、迷う場合は「Option Stores」から試してくださいとしています(What syntax should I pick?)。
なお、これら 2 つの構文については、Options API と Composition API のような実利的な違いはないようです。
Pinia 公式ページのコード例では Option Stores を使用しているものが多いことから、本レッスンでは、主に Option Stores の構文を使用して学習を進めていきます。
2. Setup Stores の動作確認
現在記述しているコードを Setup Stores の形式で書き直して動作確認を行います。
なお、本レッスンでは、ベースとして Option Stores を採用しますので、動作確認後に元の状態に戻します。
2-1. Setup Stores 構文でコードを記述
それでは、実際にコードを書いていきましょう。
counter.js
ファイルを次のように修正します。
全てのコードをテキストで表示
src\stores\counter.js
// import { defineStore } from 'pinia'
// export const useCounterStore = defineStore('counter', {
// state: () => {
// return {
// count: 0,
// name: 'Eduardo'
// }
// },
// getters: {
// doubleCount: (state) => state.count * 2,
// },
// actions: {
// increment() {
// this.count++
// },
// },
// })
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const name = ref('Eduardo')
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, name, doubleCount, increment }
})
Option Stores で記述していた部分は、後で復活させるためコメントアウトとしておいてください。
新たにしたコードは次のとおりです。
src\stores\counter.js
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const name = ref('Eduardo')
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, name, doubleCount, increment }
})
先に記載した構文例と同じのため、解説は省略します。
2-2. ブラウザで動作確認
それでは、ブラウザで開いて動作確認をしてみましょう。
「加算」ボタンをクリックして、次のように正しく値が変更されていれば OK です。
2-3. コードを元に戻す
確認が終わりましたら、コードを元に戻します。
次のように、Option Stores の構文を復活させ、Setup Stores の記述を削除(またはコメントアウト)してください。
全てのコードをテキストで表示
src\stores\counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => {
return {
count: 0,
name: 'Eduardo'
}
},
getters: {
doubleCount: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
// import { ref, computed } from 'vue'
// import { defineStore } from 'pinia'
// export const useCounterStore = defineStore('counter', () => {
// const count = ref(0)
// const name = ref('Eduardo')
// const doubleCount = computed(() => count.value * 2)
// function increment() {
// count.value++
// }
// return { count, name, doubleCount, increment }
// })
3. Store のデータをプロパティごとに分割する方法
3-1. state をプロパティごとに分割する構文
先に記述したコードでは、次のように store
のインスタンスを取得して、そのまま store
をテンプレートで使用していました。
script
import { useCounterStore } from '../stores/counter'
const store = useCounterStore()
template(store を介してプロパティを取得)
<div>count: {{ store.count }}</div>
<div>doubleCount: {{ store.doubleCount }}</div>
ここでは、次のようにインスタンスをプロパティに分割して、取得する方法について確認していきます。
const { count, doubleCount } = store // × この方式では分割取得できない
なお、上記のような一般的な記述では、取得したプロパティのリアクティブ性は失われてしまいます。
リアクティブ性を保持したままプロパティを分割するには、以下のように storeToRefs()
関数というものを使用する必要があります。
script
import { useCounterStore } from '../stores/counter'
import { storeToRefs } from 'pinia'
const store = useCounterStore()
const { count, doubleCount } = storeToRefs(store)
template
<div>count: {{ count }}</div>
<div>doubleCount: {{ doubleCount }}</div>
3-2. 具体例を使用した動作確認
① コードの修正
src\components\Home.vue
コンポーネントに次の赤枠部分を追加します。
本来は storeToRefs()
関数を使用すべきところですが、比較のため、最初は「一般的なオブジェクトの分割記法」を使用しています。
全てのコードをテキストで表示
src\components\Home.vue
<script setup>
import { useCounterStore } from '../stores/counter'
const store = useCounterStore()
const { count, doubleCount } = store
</script>
<template>
<div>Home</div>
<button @click="store.increment">加算</button>
<div>name: {{ store.name }}</div>
<div>count: {{ store.count }}</div>
<div>doubleCount: {{ store.doubleCount }}</div>
<div>分割 count: {{ count }}</div>
<div>分割 doubleCount: {{ doubleCount }}</div>
</template>
② ブラウザで動作確認
ブラウザで開いて「加算」ボタンを押してみましょう。
新たに追加した「分割 count:」および「分割 doubleCount:」の値が初期値のまま変わらないことが確認できます。
つまり、リアクティブ化されていないということになります。
これをリアクティブにするには、以下のように storeToRefs()
関数を使用します。
③ コードの追加修正
Home.vue
コンポーネントを storeToRefs()
関数を使用して、次のように修正します。
全てのコードをテキストで表示
src\stores\counter.js
<script setup>
import { useCounterStore } from '../stores/counter'
import { storeToRefs } from 'pinia'
const store = useCounterStore()
const { count, doubleCount } = storeToRefs(store)
</script>
<template>
<div>Home</div>
<button @click="store.increment">加算</button>
<div>name: {{ store.name }}</div>
<div>count: {{ store.count }}</div>
<div>doubleCount: {{ store.doubleCount }}</div>
<div>分割 count: {{ count }}</div>
<div>分割 doubleCount: {{ doubleCount }}</div>
</template>
④ 修正後の動作確認
再びブラウザで開いて「加算」ボタンを押してみましょう。
今度は、クリックするたびに、「分割 count:」および「分割 doubleCount:」の値が連動して加算することが確認できると思います。
3-3. actions は直接分解が可能
なお、リアクティブ変数でない actions(メソッド)は、次のように分割取得することが可能です。
const { increment } = store
ここでは、特に動作確認をしませんが、興味があればご自身で確認をしていただければと思います。
4. 複数の Store を定義する
次に、Store を複数定義する方法です。
一般的には、役割ごとに Store を作成して状態(state)を管理することになります。
方法は簡単で、src\stores
ディレクトリ内に、Store ファイルを追加するだけで OK です。
早速ですが、プロジェクトに Store を追加してみましょう。
4-1. user ストアを追加
以下のように、src\stores
ディレクトリに、user.js
ファイルを追加します。
記述したコードは次のとおりです。
src\stores\user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => {
return {
id: 1
}
}
})
Setup Stores 構文で記述した場合
参考までに、Setup Stores 構文で記述した場合は次のようになります。
src\stores\user.js
import { ref } from 'vue'
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', () => {
const id = ref(1)
return { id }
})
state のみの簡単な構成で、id
の初期値は 1
としています。
以上で、Store の追加については完了です。
4-2. App.vue コンポーネントの修正
新しく作成した user ストアを使用してみましょう。
src\App.vue
コンポーネントのうち、userId
を定義していた部分(緑枠部分)を削除して、代わりに user ストアの id
を当てるようにします。
追加・修正するのは赤枠の部分となります。
全てのコードをテキストで表示
src\stores\user.js
<script setup>
import { useUserStore } from './stores/user'
const userStore = useUserStore()
</script>
<template>
<div>ユーザーID: <input v-model="userStore.id"/></div>
<p>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
<router-link :to="`/user/${userStore.id}`">UserPage</router-link>
</p>
<router-view></router-view>
</template>
<style scoped>
</style>
上記のように、Store の state(ここでは id
)を、v-model を使用して双方向データバインディングとすることが可能です(※)。
Pinia における双方向データバインディング(v-model)
Store の state(データ)を更新するには、一般的には actions を使用することになります。
しかし、Pinia では、state のプロパティを、v-model で紐づけて双方向データバインディングをすることを許容しています(GitHub のディスカッション参照)。
actions を介すかどうかは、実装者側で判断することになります。
4-3. ブラウザで動作確認
正しく動作するか、ブラウザで確認しておきましょう。
次のように、ユーザー ID 欄に 3
と入力の上「UserPage」をクリックします。
user ストアの id
との双方向バインディングができていれば、以上のように、正しくページ遷移できるはずです。

Lesson 8
Chapter 4
State(ステート)
1. 基本的な使用方法
1-1. State の定義
State(ステート)は、Store の核となる部分であり、状態(データ)を保持する関数として定義されます。
① Option Stores 構文
Option Stores 構文では、以下のように定義します。
import { defineStore } from 'pinia'
export const useStore = defineStore('storeId', {
// 型推論のためにアロー関数が推奨される
state: () => {
return {
// プロパティの型は自動的に型推論される
count: 0,
name: 'Eduardo',
isAdmin: true,
items: [],
hasChanged: true,
}
},
})
型推論(※)を正しく効かせるためには、上記のようにアロー関数の形式で定義することが推奨されています。
型推論
型推論とは、変数などに明示的にデータ型を指定しなくとも、コンパイラなどが自動的にそのデータ型を決定する機能のことをいいます。
Pinia は Vuex と比較してこの型推論の機能が充実しており、TypeScript を使用した Vue の開発と相性が良いこともその特長となっています。
② Setup Stores 構文
Setup Stores 構文を使用して State を定義する場合は、次のように ref
関数を使用します。
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const name = ref('Eduardo')
const isAdmin = ref(true)
const items = ref([])
const hasChanged = ref(true)
return { count, name, isAdmin, items, hasChanged }
})
1-2. コンポーネントから State へのアクセス
次に、コンポーネントから State を使用する方法です。
これまでの例でも見てきましたが、Store のインスタンス(ここでは userStore
)を介して State にアクセスすることで、状態を直接読み書きすることができます。
<script setup>
import { useUserStore } from './stores/user'
const userStore = useUserStore()
userStore.id++
</script>
<template>
<div>ユーザーID: <input v-model="userStore.id"/></div>
</template>
2. State のメソッド
State には、以下のようなメソッドが用意されています。
これにより、State の内容を初期状態に戻したり、指定した値で更新することができます。
No | メソッド | 説明 |
---|---|---|
1 |
$reset()
|
State を初期値にリセット |
2 |
$patch()
|
State を指定した内容で更新 |
3 |
$state()
|
State を交換する(内部で $patch() が呼び出される)
|
2-1. $reset() メソッド
① 基本構文
$reset()
メソッドの基本構文は次のとおりです。
const store = useStore()
store.$reset()
② コードの修正
実際にコードを記載して動作確認をしてみましょう。
src\components\Home.vue
コンポーネントに赤枠部分の 1 行を追加します。
コードの整理のため、緑枠の部分は削除しておきます(コメントアウトでも OK です)。
全てのコードをテキストで表示
src\components\Home.vue
<script setup>
import { useCounterStore } from '../stores/counter'
const store = useCounterStore()
</script>
<template>
<div>Home</div>
<button @click="store.increment">加算</button>
<button @click="store.$reset()">リセット</button>
<div>name: {{ store.name }}</div>
<div>count: {{ store.count }}</div>
<div>doubleCount: {{ store.doubleCount }}</div>
</template>
追加した 1 行は、次の部分です。
<button @click="store.$reset()">リセット</button>
このリセットボタンを押すと、$reset()
メソッドが実行され、state の値が初期値にリセットされます。
③ 動作確認
ブラウザで開いて、まず「加算」ボタンを何回か押して count
の値を変化させます。
次に「リセット」ボタンを押して、以下のように数値が初期化されれば OK です。
2-2. $patch() メソッド
① 基本構文($patch オブジェクトによる更新)
$reset()
メソッドの基本構文は次のとおりです。
更新する state のプロパティと値のセットを指定します。
全てのプロパティを指定する必要はなく、更新したいプロパティのみ指定します。
store.$patch({
count: store.count + 1,
age: 120,
name: 'DIO',
})
② 基本構文($patch 関数による更新)
なお、次のように、アロー関数の形式で記述することもできます。
引数で受け取る state
を使用することで、配列型のプロパティへの要素の追加(push
)などの処理も可能となります。
store.$patch((state) => {
state.items.push({ name: 'shoes', quantity: 1 })
state.hasChanged = true
})
③ コードの修正
こちらも、実際にコードを記載して動作確認をしてみましょう。
src\components\Home.vue
コンポーネントに赤枠部分を追加します。
全てのコードをテキストで表示
src\components\Home.vue
<script setup>
import { useCounterStore } from '../stores/counter'
const store = useCounterStore()
const onPatch = () => {
store.$patch({
count: store.count - 1,
name: 'DIO',
})
}
</script>
<template>
<div>Home</div>
<button @click="store.increment">加算</button>
<button @click="store.$reset()">リセット</button>
<button @click="onPatch">更新</button>
<div>name: {{ store.name }}</div>
<div>count: {{ store.count }}</div>
<div>doubleCount: {{ store.doubleCount }}</div>
</template>
追加したのは、次の部分です。
script
const onPatch = () => {
store.$patch({
count: store.count - 1,
name: 'DIO',
})
}
template
<button @click="onPatch">更新</button>
上記の「更新」ボタンを押すと、onPatch
メソッドが呼び出され、script 側で定義した $patch
が実行されます。
④ 動作確認
ブラウザを開いて確認をしてみましょう。
「更新」ボタンを押すと表示内容が更新され、押すたびに count
の値が 1
ずつ減算されていくことが確認できるはずです。
2-3. $state() メソッド
① 基本構文
$state()
メソッドの基本構文は次のとおりです。指定した値で state を置き換えます。
全てのプロパティを指定する必要はなく、置換えを行うプロパティのみ指定します。
store.$state = { count: 24 }
$state()
メソッドの実行時には、内部的に $patch()
メソッドが呼び出されるため、実際は次の処理を行っていることと同義になります。
store.$patch((state) => { state.count: 24 })
② コードの修正
$state()
についても、実際にコードを記載して動作確認をしてみましょう。
src\components\Home.vue
コンポーネントに赤枠部分を追加します。
全てのコードをテキストで表示
src\components\Home.vue
<script setup>
import { useCounterStore } from '../stores/counter'
const store = useCounterStore()
const onPatch = () => {
store.$patch({
count: store.count - 1,
name: 'DIO',
})
}
const onState = () => {
store.$state = { count: 5 }
}
</script>
<template>
<div>Home</div>
<button @click="store.increment">加算</button>
<button @click="store.$reset()">リセット</button>
<button @click="onPatch">更新</button>
<button @click="onState">置換え</button>
<div>name: {{ store.name }}</div>
<div>count: {{ store.count }}</div>
<div>doubleCount: {{ store.doubleCount }}</div>
</template>
追加したのは、次の部分です。
script
const onState = () => {
store.$state = { count: 5 }
}
template
<button @click="onState">置換え</button>
上記の「置換え」ボタンを押すと、onState
メソッドが呼び出され、script 側で定義した $state
が実行されます。
③ 動作確認
ブラウザを開いて確認をしてみましょう。
「置換え」ボタンを押すたびに count
の値が 5
に変わります。
なお「更新」ボタンを押して「置換え」ボタンを押すと、name
は置換えの対象ではないことから「DIO」のまま変わらないことも確認してみてください。
3. State の監視
$subscribe()
メソッドを使用することにより、State の値の変化を監視することができます。
3-1. 基本構文
$subscribe()
メソッドの基本構文は次のとおりです。
store.$subscribe((mutation, state) => {
// 変異のタイプ('direct' | 'patch object' | 'patch function')を取得
mutation.type
// 対象 Store の Id を取得
mutation.storeId
// $patch() メソッドにオブジェクトを渡した場合のみ変更内容を取得
mutation.payload
// 変更後の state の内容を取得(必要な処理を行う)
console.log(state)
})
$subscribe()
メソッドに指定するコールバック関数では、第 1 引数の mutation
から変更のタイプなどを取得することができ、第 2 引数の state
からは、変更後の state の値が取得できます。
① mutation のプロパティ
第 1 引数の mutation
から取得できるプロパティは次のとおりです。
No | プロパティ | 説明 |
---|---|---|
1 | type | MutationType(変異のタイプ)を取得(※種類は次の表を参照) |
2 | storeId |
対象 Store の Id を取得(defineStore() メソッドの第 1 引数に指定したストア名です)
|
3 | payload |
$patch() メソッドにオブジェクトを渡した場合(type が 'patch object' の場合)のみ変更内容を取得
|
② MutationType(変異のタイプ)
mutation.type
から取得されるタイプは、次の 3 種類です。
MutationType | 例 | 説明 |
---|---|---|
direct |
store.name = 'new name'
|
state を直接変更 |
patch function |
store.$patch(state => state.name = 'newName')
|
$patch 関数で state を変更
|
patch object |
store.$patch({ name: 'newName' })
|
$patch オブジェクトで state を変更
|
$patch
($state
を含む)以外の方法で state を更新した場合の MutationType は、基本的に「direct」となります。
実際に確認した方が早いので、以下、コードを記述して確認していきます。
3-2. コードの修正
src\components\Home.vue
コンポーネントに赤枠部分を追加します。
全てのコードをテキストで表示
src\components\Home.vue
<script setup>
import { useCounterStore } from '../stores/counter'
const store = useCounterStore()
const onPatch = () => {
store.$patch({
count: store.count - 1,
name: 'DIO',
})
}
const onState = () => {
store.$state = { count: 5 }
}
store.$subscribe((mutation, state) => {
console.log(mutation.type)
console.log(mutation.storeId)
console.log(mutation.payload)
console.log(state)
})
</script>
<template>
<div>Home</div>
<button @click="store.increment">加算</button>
<button @click="store.$reset()">リセット</button>
<button @click="onPatch">更新</button>
<button @click="onState">置換え</button>
<div>name: {{ store.name }}</div>
<div>count: {{ store.count }}</div>
<div>doubleCount: {{ store.doubleCount }}</div>
</template>
追加したのは、次の部分です。
store.$subscribe((mutation, state) => {
console.log(mutation.type)
console.log(mutation.storeId)
console.log(mutation.payload)
console.log(state)
})
state の変更をキャッチしたときに、mutation
と state
の内容を Console 画面に表示していきます。
3-3. 動作確認
それでは、ブラウザで開いて確認をしましょう。
次のように、開発者画面から Console 画面を開いておきます。
① actions による変更
まず「加算」ボタンを押します。加算ボタンは actions から値を更新します。
mutation.type
は「direct
」、mutation.storeId
は「counter」と表示されています。
$patch
オブジェクトは使用していないので、mutation.payload
は「undefined」です。
なお、当然ですが、最後の state
は、画面の表示内容と一致しています。
② $reset() による変更
次に $reset()
メソッドを実行する「リセット」ボタンを押します。
mutation.type
は「patch function
」となっており、$reset()
メソッドは、$patch
関数を使用していることが分かります。
③ $patch() による変更
続いて $patch()
メソッドを実行する「更新」ボタンを 2 回押します。
$patch()
オブジェクトを使用しているため、mutation.type
は「patch object
」となっています。
mutation.payload
には、$patch()
オブジェクトとして指定された内容が、それぞれ取得されています。
④ $state() による変更
最後に $state()
メソッドを実行する「置換え」ボタンを押します。
mutation.type
は「patch function
」となっていることから、内部的に $patch
関数が実行されていることが分かります。
以上、$subscribe()
メソッドについて、一通りの動作確認を行いました。
実務においては、引数の mutation
および state
から得られる値を使用しつつ、必要な処理を実装していくことになります。

Lesson 8
Chapter 5
Getters(ゲッター)
1. 基本的な使用方法
1-1. Getters の定義例
基本的に、Getters(ゲッター)は、State の値を用いた計算値を返却します。
① Option Stores 構文
Option Stores 構文では、以下のように、第 1 引数に state
を受け取るアロー関数式で定義をします。
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
getters: {
doubleCount: (state) => state.count * 2,
},
})
② Setup Stores 構文
Setup Stores 構文で Getters を定義する場合は、computed
を使用します。
つまり、Getters は、State の値を用いた Computed(算出プロパティ)と同義ということになります。
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
return { count, doubleCount }
})
1-2. コンポーネントから Getters へのアクセス
次のように、Store のインスタンスを介して、コンポーネントから Getters にアクセスすることができます。
<script setup>
import { useCounterStore } from './stores/counter'
const store = useCounterStore()
</script>
<template>
<p>Double count is {{ store.doubleCount }}</p>
</template>
2. Store 内における他の Getters へのアクセス
2-1. 定義例
下記コード中の doubleCountPlusOne()
のように、this
を使用して、他の Getters(ここでは doubleCount
)を組み合わせて計算値を返却することができます。
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
getters: {
doubleCount: (state) => state.count * 2,
doubleCountPlusOne() {
return this.doubleCount + 1
},
},
})
なお、this
を使用する場合は、アロー関数式は使用できないため、一般的な関数の形式で記述する必要があります。
さらに、型推論が効かなくなりますが、このカリキュラムでは特に気にしなくとも大丈夫です(※)。
型の定義を行う場合
型を定義するには、次のような方法があります。
① TypeScript で型定義を行う
TypeScript を使用すると厳密な型定義をすることができます(TypeScript は本カリキュラムでは使用しません)。
doubleCountPlusOne(): number {
return this.doubleCount + 1
},
② JSDoc を使用して型のヒントを与える
TypeScript を使用しない場合は、JSDoc という JavaScript 用のコメント記法を用いることで、型のヒントを追加することも可能です。
/**
* @returns {number}
*/
doubleCountPlusOne() {
return this.doubleCount + 1
},
VSCode で定義すると、次のようになります。
Getters 関数にカーソルを合わせると型のヒントが表示されます。
2-2. 具体例で確認
それでは、 Store 内の他の Getters を使用する場合について、実際にコードを書いて確認してみましょう。
① counter ストアの修正
src\stores\counter.js
ファイルに、次の赤枠部分を追加します。
全てのコードをテキストで表示
src\stores\counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => {
return {
count: 0,
name: 'Eduardo'
}
},
getters: {
doubleCount: (state) => state.count * 2,
doubleCountPlusOne() {
return this.doubleCount + 1
},
},
actions: {
increment() {
this.count++
},
},
})
② Home.vue コンポーネントの修正
src\components\Home.vue
ファイルの template に、次の赤枠部分を追加します。
全てのコードをテキストで表示
src\components\Home.vue
<script setup>
import { useCounterStore } from '../stores/counter'
const store = useCounterStore()
const onPatch = () => {
store.$patch({
count: store.count - 1,
name: 'DIO',
})
}
const onState = () => {
store.$state = { count: 5 }
}
store.$subscribe((mutation, state) => {
console.log(mutation.type)
console.log(mutation.storeId)
console.log(mutation.payload)
console.log(state)
})
</script>
<template>
<div>Home</div>
<button @click="store.increment">加算</button>
<button @click="store.$reset()">リセット</button>
<button @click="onPatch">更新</button>
<button @click="onState">置換え</button>
<div>name: {{ store.name }}</div>
<div>count: {{ store.count }}</div>
<div>doubleCount: {{ store.doubleCount }}</div>
<div>doubleCountPlusOne: {{ store.doubleCountPlusOne }}</div>
</template>
③ ブラウザで動作確認
ブラウザで開いて「加算」ボタンを何回か押してみましょう。
新しく追加した「doubleCountPlusOne」の数値が、doubleCount に 1 加算した値で変動することが確認できると思います。
3. 他の Store の Getters へのアクセス
3-1. 定義例
次のように他の Store をインポートすることで、その Store の Getters にアクセスすることもできます(公式サイト参照)。
import { useOtherStore } from './other-store'
export const useStore = defineStore('main', {
state: () => ({
// ...
}),
getters: {
otherGetter(state) {
const otherStore = useOtherStore()
return state.localData + otherStore.data
},
},
})
上記のように Getters の関数内で、他の Store のインスタンス(ここでは otherStore
)を取得してそのまま使用することになります。
3-2. 具体例で確認
それでは、他の Store の Getters を使用する場合について、実際にコードを書いて確認してみましょう。
① user ストアの修正
src\stores\user.js
ファイルに Getters を 1 つ追加します(赤枠部分)。
id
に 10000 を加算したものを globalId
として取得するだけのものです。
全てのコードをテキストで表示
src\stores\user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => {
return {
id: 1
}
},
getters: {
globalId: (state) => state.id + 10000
}
})
② counter ストアの修正
src\stores\counter.js
ファイルに、次の赤枠部分を追加します。
全てのコードをテキストで表示
src\stores\counter.js
import { defineStore } from 'pinia'
import { useUserStore } from './user'
export const useCounterStore = defineStore('counter', {
state: () => {
return {
count: 0,
name: 'Eduardo'
}
},
getters: {
doubleCount: (state) => state.count * 2,
doubleCountPlusOne() {
return this.doubleCount + 1
},
idAndCount: (state) => {
const userStore = useUserStore()
return `${userStore.globalId}_${state.count}`
},
},
actions: {
increment() {
this.count++
},
},
})
user ストアをインポートして、それを idAndCount
という Getters で読み込んで使用しています。
返却値は、user ストアから取得した globalId
と、counter ストアの count
を文字列として結合したものを返します。
③ App.vue コンポーネントの修正
src\App.vue
ファイルに次の赤枠部分を追加します。
v-model に number
修飾子を使用すると、テキストボックスに入力した値を数値型としてバインドすることができます(これを入れないと入力値が文字列として認識され、四則演算をすることができません)。
全てのコードをテキストで表示
src\App.vue
<script setup>
import { useUserStore } from './stores/user'
const userStore = useUserStore()
</script>
<template>
<div>ユーザーID: <input v-model.number="userStore.id"/></div>
<p>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
<router-link :to="`/user/${userStore.id}`">UserPage</router-link>
</p>
<router-view></router-view>
</template>
<style scoped>
</style>
④ Home.vue コンポーネントの修正
最後に、src\components\Home.vue
コンポーネントの template に、次の赤枠部分を追加します。
全てのコードをテキストで表示
src\components\Home.vue
<script setup>
import { useCounterStore } from '../stores/counter'
const store = useCounterStore()
const onPatch = () => {
store.$patch({
count: store.count - 1,
name: 'DIO',
})
}
const onState = () => {
store.$state = { count: 5 }
}
store.$subscribe((mutation, state) => {
console.log(mutation.type)
console.log(mutation.storeId)
console.log(mutation.payload)
console.log(state)
})
</script>
<template>
<div>Home</div>
<button @click="store.increment">加算</button>
<button @click="store.$reset()">リセット</button>
<button @click="onPatch">更新</button>
<button @click="onState">置換え</button>
<div>name: {{ store.name }}</div>
<div>count: {{ store.count }}</div>
<div>doubleCount: {{ store.doubleCount }}</div>
<div>doubleCountPlusOne: {{ store.doubleCountPlusOne }}</div>
<div>idAndCount: {{ store.idAndCount }}</div>
</template>
⑤ ブラウザで動作確認
それでは、ブラウザで表示してみましょう。
「加算」ボタンを何回か押すと、それに連動して「idAndCount」の末尾の数値がリアクティブに変動することが確認できます。
次に「ユーザーID」欄に 123
という数字を入力します
すると、入力に連動して「idAndCount」の前半部分の数値がリアクティブに変動します。
この前半部分の数値は、他の Store である user ストアの Getters から取得したものです。
以上のとおり、複数の Store から取得した値でも、リアクティブに反映されることが確認できました。
4. Getters に引数を渡す
4-1. 定義例
基本的に Getters に引数を渡すことはできません。ただし、Getters の戻り値を関数とすることで引数を渡すことが可能になります(公式サイト参照)。
export const useStore = defineStore('main', {
getters: {
getUserById: (state) => {
return (userId) => state.users.find((user) => user.id === userId)
},
},
})
コンポーネント側では次のように使用します。
<script setup>
const store = useStore()
</script>
<template>
<p>User 2: {{ store.getUserById(2) }}</p>
</template>
4-2. 具体例で確認
それでは、実際にコードを書いて確認してみましょう。
① counter ストアの修正
src\stores\counter.js
ファイルに、次の赤枠部分を追加します。
全てのコードをテキストで表示
src\stores\counter.js
import { defineStore } from 'pinia'
import { useUserStore } from './user'
export const useCounterStore = defineStore('counter', {
state: () => {
return {
count: 0,
name: 'Eduardo'
}
},
getters: {
doubleCount: (state) => state.count * 2,
doubleCountPlusOne() {
return this.doubleCount + 1
},
idAndCount: (state) => {
const userStore = useUserStore()
return `${userStore.globalId}_${state.count}`
},
multiplyCount: (state) => {
return (num) => state.count * num
},
},
actions: {
increment() {
this.count++
},
},
})
multiplyCount
という Getters を追加しています。
引数に数値 num
をとり、返却値として count
と num
を乗算した値を返します。
② Home.vue コンポーネントの修正
src\components\Home.vue
コンポーネントの template に、次の赤枠部分を追加します。
全てのコードをテキストで表示
src\components\Home.vue
<script setup>
import { useCounterStore } from '../stores/counter'
const store = useCounterStore()
const onPatch = () => {
store.$patch({
count: store.count - 1,
name: 'DIO',
})
}
const onState = () => {
store.$state = { count: 5 }
}
store.$subscribe((mutation, state) => {
console.log(mutation.type)
console.log(mutation.storeId)
console.log(mutation.payload)
console.log(state)
})
</script>
<template>
<div>Home</div>
<button @click="store.increment">加算</button>
<button @click="store.$reset()">リセット</button>
<button @click="onPatch">更新</button>
<button @click="onState">置換え</button>
<div>name: {{ store.name }}</div>
<div>count: {{ store.count }}</div>
<div>doubleCount: {{ store.doubleCount }}</div>
<div>doubleCountPlusOne: {{ store.doubleCountPlusOne }}</div>
<div>idAndCount: {{ store.idAndCount }}</div>
<div>multiplyCount: {{ store.multiplyCount(5) }}</div>
</template>
ゲッター multiplyCount
に、引数として 5
を渡しています。
これにより、count
に 5
を乗算した値が常に返却されることになります。
③ ブラウザで動作確認
それでは、ブラウザで表示してみましょう。
「加算」ボタンを押すたびに「multiplyCount」に、count
に 5
を乗算した数値が表示されることが確認できれば OK です。
以上、Getters に引数を渡す方法について確認することができました。

Lesson 8
Chapter 6
Actions(アクション)
1. 基本的な使用方法
1-1. Actions の定義例
Actions(アクション)には、State の値を変更する等のロジックを定義します。
Getters とは異なり、自由に引数を設定することができ、非同期処理(※)を行うこともできます。
そのため、サーバから API を呼び出したり、他のアクションを実行するなど、自由度の高い処理を記述することができるようになっています。
非同期処理
非同期処理とは、あるタスクの実行中に、その終了を待たずに別のタスクを実行する処理のことを言います。
Web アプリケーションでは「非同期通信」という言葉がよく出てきます。
これは、サーバーとの通信の際に、その応答を待たずに別の処理を進めることができる通信方法のことを指します。
① Option Stores 構文
まず、Option Stores 構文で Actions を定義する例です。
公式ページの記載例は、次のようになっています。
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
actions: {
increment() {
this.count++
},
randomizeCounter() {
this.count = Math.round(100 * Math.random())
},
},
})
基本的には、一般的なメソッド定義と変わりませんが、this
を使用するためアロー関数式での記述はできません。
② Setup Stores 構文
Setup Stores 構文で Actions を定義する場合は、メソッドとして記述します。
つまり、Actions は、コンポーネントにおけるメソッドと同義ということになります。
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const increment = () => count.value++
const randomizeCounter = () => count.value = Math.round(100 * Math.random())
return { count, doubleCount }
})
1-2. コンポーネントから Actions へのアクセス
次のように、Store のインスタンスを介して、コンポーネントから Actions にアクセスすることができます。これも、今まで見てきたとおりです。
<script setup>
const store = useCounterStore()
store.randomizeCounter()
</script>
1-3. 他の Store の Actions へのアクセス
次のように他の Store をインポートすることで、その Store のアクション(Actions)にアクセスすることもできます(公式サイトより)。
import { useAuthStore } from './auth-store'
export const useSettingsStore = defineStore('settings', {
state: () => ({
preferences: null,
// ...
}),
actions: {
async fetchUserPreferences() {
const auth = useAuthStore()
if (auth.isAuthenticated) {
this.preferences = await fetchPreferences()
} else {
throw new Error('User must be authenticated')
}
},
},
})
上記の例では、アクション内で、const auth = useAuthStore()
というように他の Store を呼び出し、そのアクション(ここでは isAuthenticated
)を使用する形になっています。
この使用方法は、他の Store のゲッター(Getters)を使用する場合と基本的に同じのため、実例での確認は省略します。
2. Actions で非同期通信を行う
先に述べましたように、Actions で非同期通信を使用することができます。
ここでは、実際に API を使用した非同期通信を実装してみましょう。
2-1. 使用する API
気象庁が提供している天気予報の API を使用します。
https://www.jma.go.jp/bosai/forecast/data/overview_forecast/130000.json
このリンクをクリックすると、東京の天気の概況が JSON 形式で取得できます。
取得された JSON オブジェクトの内容を開発者用画面の「Network」タブの「Preview」から確認しておきます。
上の画像より、この API で取得できるプロパティは次の 5 つということが確認できます。
・headlineText
・publishingOffice
・reportDatetime
・targetArea
・text
2-2. weather ストアの追加
src\stores
ディレクトリに、weather.js
ファイルを追加してください。
weather.js
ファイルに記述したコードは次のとおりです。
src\stores\weather.js
import { defineStore } from 'pinia'
export const useWeatherStore = defineStore('weather', {
state: () => {
return {
overview: {
headlineText: '',
publishingOffice: '',
reportDatetime: '',
targetArea: '',
text: '',
}
}
},
actions: {
async fetchForecast() {
const url = 'https://www.jma.go.jp/bosai/forecast/data/overview_forecast/130000.json'
const response = await fetch(url);
this.overview = await response.json();
}
}
})
以下、記載している内容について見ていきます。
① state の定義内容
state には、API から取得したデータを格納するための overview
オブジェクトを定義しています。
overview: {
headlineText: '',
publishingOffice: '',
reportDatetime: '',
targetArea: '',
text: '',
}
この overview
オブジェクトの各プロパティは、先ほど、実際の API から確認したプロパティをそのまま定義しています。
② actions の定義内容
actions には、fetchForecast()
という関数を定義しています。
async fetchForecast() {
const url = 'https://www.jma.go.jp/bosai/forecast/data/overview_forecast/130000.json'
const response = await fetch(url);
this.overview = await response.json();
}
・fetch() 関数
サーバとの通信には、fetch()
関数を使用しています。この関数は非同期で使用する必要があるため、async、await を使用して API の呼出しを実装しています。
今回のように、特定の URL からデータを取得する場合は、fetch('指定URL')
の振り合いで、引数に URL を指定するだけで値の取得が可能となっています。
・json() 関数
json()
関数を使用することで、JSON 形式のデータを JavaScript のオブジェクト形式に変換することができます。
2-3. About.vue コンポーネントの修正
About.vue
コンポーネントに赤枠部分を追加して、緑枠部分を削除します。
修正後の About.vue
ファイルは次のようになります。
src\components\About.vue
<script setup>
import { useWeatherStore } from '../stores/weather'
const weatherStore = useWeatherStore()
</script>
<template>
<div>About</div>
<button @click="weatherStore.fetchForecast">天気取得</button>
<table>
<tr><th>提供元</th><td>{{ weatherStore.overview.publishingOffice }}</td></tr>
<tr><th>報告日時</th><td>{{ weatherStore.overview.reportDatetime }}</td></tr>
<tr><th>場所</th><td>{{ weatherStore.overview.targetArea }}</td></tr>
<tr><th>概要</th><td>{{ weatherStore.overview.text }}</td></tr>
</table>
</template>
<style scoped>
tr {
vertical-align: top;
text-align: left;
white-space: pre-wrap;
}
th {
width: 80px;
}
</style>
以下、記載している内容のうち Actions に関係のあるところを確認しておきます。
① weather ストアのインスタンス取得
次のところで、weather ストアのインスタンスを取得しています。
import { useWeatherStore } from '../stores/weather'
const weatherStore = useWeatherStore()
指定内容は、特に今までと変わりません。
② API 呼出しの実行
API の呼出しを行う fetchForecast
アクションは、次のボタンで呼び出されます。
<button @click="weatherStore.fetchForecast">天気取得</button>
2-4. ブラウザで動作確認
それでは、ブラウザで表示して、動作確認をしてみましょう。
「天気取得」ボタンを押してみましょう。
成功すれば、次のように API で取得したデータが画面に表示されるはずです。
3. Action の監視
store.$onAction()
メソッドを使用することにより、Action の監視をすることができます。
3-1. 基本構文
store.$onAction()
メソッドの基本構文は次のとおりです。
store.$onAction(({ name, store, args, after, onError }) => {
console.log(name) // アクション名
console.log(store.$id) // store のインスタンス
console.log(args) // アクションの引数
after((result) => console.log(result)) // アクション成功後の処理(引数 result は戻り値)
onError((error) => console.log(error)) // アクション失敗時の処理
})
対象の Store 内のアクションが実行されると、store.$onAction()
メソッドも発火します。
引数から 5 つのプロパティ name
,store
,args
,after
,onError
が取得できます。
No | プロパティ | 説明 |
---|---|---|
1 | name | 実行されたアクション名を取得 |
2 | store | 対象 store のインスタンスを取得 |
3 | args | 実行されたアクションの引数を取得 |
3 | after() | アクション成功後の処理をコールバック関数で記述(引数 result は戻り値) |
3 | onError() | アクション失敗時の処理をコールバック関数で記述 |
3-2. 具体例で確認
それでは、store.$onAction()
について、実際にコードを書いて確認してみましょう。
① weather ストアの修正
動作確認のため、weather ストアのアクション fetchForecast
に引数と戻り値を追加しておきます。
src\stores\weather.js
ファイルの、次の赤枠部分を追加・修正してください。
全てのコードをテキストで表示
src\stores\counter.js
import { defineStore } from 'pinia'
export const useWeatherStore = defineStore('weather', {
state: () => {
return {
overview: {
headlineText: '',
publishingOffice: '',
reportDatetime: '',
targetArea: '',
text: '',
}
}
},
actions: {
async fetchForecast(code) {
const url = `https://www.jma.go.jp/bosai/forecast/data/overview_forecast/${code}.json`
const response = await fetch(url)
this.overview = await response.json()
return 'success!'
}
}
})
引数に code
を追加しています。
この値が 130000
の場合は東京の天気概要が表示されることになります。
② About.vue コンポーネントの修正
src\components\About.vue
ファイルにつき、次の赤枠部分を追加・修正します。
全てのコードをテキストで表示
src\components\About.vue
<script setup>
import { ref } from 'vue'
import { useWeatherStore } from '../stores/weather'
const code = ref('130000')
const weatherStore = useWeatherStore()
const fetchForecast = async () => {
try {
await weatherStore.fetchForecast(code.value)
} catch {
console.log('アクションが実行できませんでした。')
}
}
weatherStore.$onAction(({ name, store, args, after, onError }) => {
console.log(name)
console.log(store.$id)
console.log(args)
after((result) => console.log(result))
onError((error) => console.log(error))
})
</script>
<template>
<div>About</div>
<button @click="fetchForecast">天気取得</button>
<div>コード: <input v-model="code"/></div>
<table>
<tr><th>提供元</th><td>{{ weatherStore.overview.publishingOffice }}</td></tr>
<tr><th>報告日時</th><td>{{ weatherStore.overview.reportDatetime }}</td></tr>
<tr><th>場所</th><td>{{ weatherStore.overview.targetArea }}</td></tr>
<tr><th>概要</th><td>{{ weatherStore.overview.text }}</td></tr>
</table>
</template>
<style scoped>
tr {
vertical-align: top;
text-align: left;
white-space: pre-wrap;
}
th {
width: 80px;
}
</style>
見てのとおりですが、store.$onAction()
メソッドを使用しているのは、次のところです。
これで weather
ストアのアクションを監視することができます。
weatherStore.$onAction(({ name, store, args, after, onError }) => {
console.log(name)
console.log(store.$id)
console.log(args)
after((result) => console.log(result))
onError((error) => console.log(error))
})
以下のところで、fetchForecast
アクションを script 内のメソッドにして、実行時にエラーが出た場合の処理を加えました(これをせずとも実行はできますが、ブラウザの Console に無用なエラーを表示させないための処置です)。
const fetchForecast = async () => {
try {
await weatherStore.fetchForecast(code.value)
} catch {
console.log('アクションが実行できませんでした。')
}
}
なお、引数の code
は、次の input ボックスで指定できるようにしています。
<div>コード: <input v-model="code"/></div>
③ ブラウザで動作確認
ブラウザで開いて「About」リンクをクリックして About ページを開きます。
開発者用画面の Console も表示しておいてください。
・成功時の実行結果
「コード」欄に「140000」と入力してから、「天気取得」ボタンをクリックします。
すると、次のように、神奈川県の天気概要が取得できます。
上図の右側に、アクション名「fetchForecast」、ストアの ID 名「weather」、アクションの引数「140000」、成功時の処理として戻り値が取得できているのが確認できると思います。
引数は、配列形式で取得されます。
・失敗時の実行結果
次に、「コード」欄に「140001」と入力してから、「天気取得」ボタンをクリックします。
この場合は、該当するコードが存在しないため API 取得時にエラーが生じます。
上図の右側に、アクション名「fetchForecast」、ストアの ID 名「weather」、アクションの引数「140001」が表示されていることが確認できます。
加えて、エラー時の処理として、エラーオブジェクトの内容(赤枠部分)が表示されていることも確認できます。
以上、本レッスンでは、状態管理の方法、とりわけ Vue の公式ライブラリである Pinia について学んできました。
状態管理は、現在のフロントエンド開発において必須の技術となります。Store の概念や、データの取得/更新の仕組みについて、よく押さえておくようにしてください。
