こんにちは。
私はこれまで、Reactを使ったアプリ開発を行ってきましたが、最近はVueを使った開発にも携わるようになりました。
そこで理解を深めるために、フロントエンド開発で欠かせない「状態管理」について整理してみました。
本記事では、Vueにおける代表的な状態管理ライブラリである「Vuex」と「Pinia」を取り上げ、それぞれの特徴や使い方、違いを初心者向けに解説します。
そもそも状態管理とは?
アプリ開発では「ユーザー情報」「カートの中身」「ログイン状態」など、複数のコンポーネントで共有したいデータがあります。
小規模開発なら props や emit で十分ですが、コンポーネントが増えるとコードが複雑になっていきます。
そこで「アプリ全体で使う状態(state)を一箇所にまとめて管理する仕組み」が必要になってきます。
フロントエンド開発に馴染みがない方向けに説明すると、一時的なDBのようなものとイメージしてください。
Vue公式のシンプルstoreとその限界
Vue公式サイトには、「シンプルな状態管理」の例が紹介されています。
<script setup>
import { ref } from 'vue'
// 状態
const count = ref(0)
// アクション
function increment() {
count.value++
}
</script>
<!-- ビュー -->
<template>{{ count }}</template>
この例は、以下のパーツで構成される自己完結型のユニットです:
state(状態) は、アプリケーション駆動する信頼できる情報源(the source of truth)です
view(ビュー) は、state(状態) の宣言的な写像(declarative mapping)です
actions(アクション) は、view(ビュー) からのユーザー入力に反応して state を変更するための方法を提供します
引用元:状態管理
小さなアプリならこれで十分かと思います。ただ、実際サービスを作るとなると、次のような課題が出てきます。
- 状態の追跡が難しい → どこで変更されたかわからない
- ロジックが肥大化する → API呼び出しや複雑な処理を全部ここに書くと混乱
- 開発者が増えると衝突する → 誰がどう状態を変更できるかが曖昧
これらの問題を解決するため、実際のVueサービス開発では「Vuex」や「Pinia」のライブラリを使用するのが定番です。
React経験者にとっては、「Redux」や「Recoil」のような感じですね。
Vuex
Vuexは、Vueアプリのための状態管理パターン + ライブラリです。Vuexには以下のような特徴があります。
state,mutations,actions,gettersの4機能で構成- この4機能を厳密にすることで、大規模開発でも見通しやすい設計
- Vue2時代は、Vuexが標準(Vue3にも対応はしている)
- コード量が多く冗長になりがち
以下は、商品一覧を表示するサンプルコードです。
<br />// store.js
import { createStore } from 'vuex'
import axios from 'axios'
export default createStore({
state() {
return {
products: []
}
},
mutations: {
setProducts(state, products) {
state.products = products
}
},
actions: {
async fetchProducts({ commit }) {
try {
const res = await axios.get('/api/products')
commit('setProducts', res.data)
} catch (error) {
console.error('商品取得に失敗:', error)
}
}
},
getters: {
productCount: (state) => state.products.length
}
})
上記を解説すると以下です。
- state:アプリ全体で共有するデータ。初期値を設定する
- mutations:状態(state)を直接変更する唯一の場所
- actions:非同期処理やビジネスロジック(API呼び出しなど)と書く。ここでmutationsの呼び出しを行う
- getters:state を加工して返す計算用プロパティ
コンポーネントでの使用は下記です。
<template>
<div>
<h2>商品一覧 ({{ productCount }} 件)</h2>
<button @click="fetchProducts">商品を取得</button>
<ul>
<li v-for="p in products" :key="p.id">
{{ p.name }} - {{ p.price }}円
</li>
</ul>
</div>
</template>
<script>
import { mapState, mapActions, mapGetters } from 'vuex'
export default {
computed: {
...mapState(['products']),
...mapGetters(['productCount'])
},
methods: {
...mapActions(['fetchProducts'])
}
}
</script>
mapState, mapGetters, mapActionsは、Vuexのヘルパー関数です。
これを使うと、storeのデータや関数を「computed / methods」に自動でバインドしてくれます。
Pinia
Piniaは、Vue3向けの次世代状態管理ライブラリで、Vuexの後継的ポジションになります。特徴としては以下です。
- コードがシンプル→mutationsが不要、actionsだけでOK
- Vue2時代はVuexが定番でしたが、Vue3ではPiniaが公式推奨になった
- Vue3で登場したComposition APIと相性が良い
- Vue2ではそのまま使えない(プラグイン導入が必要)
同じく、商品一覧を表示するサンプルコードです。
// stores/product.js
import { defineStore } from 'pinia'
import axios from 'axios'
export const useProductStore = defineStore('product', {
state: () => ({
products: []
}),
actions: {
async fetchProducts() {
try {
const res = await axios.get('/api/products')
this.products = res.data
} catch (error) {
console.error('商品取得に失敗:', error)
}
}
},
getters: {
productCount() {
return this.products.length
}
}
})
コンポーネントでの使用は下記です。
<template>
<div>
<h2>商品一覧 ({{ productStore.productCount }} 件)</h2>
<button @click="productStore.fetchProducts">商品を取得</button>
<ul>
<li v-for="p in productStore.products" :key="p.id">
{{ p.name }} - {{ p.price }}円
</li>
</ul>
</div>
</template>
<script setup>
import { useProductStore } from '../stores/product'
const productStore = useProductStore()
</script>
コードを見比べるだけでも、Piniaの方がかなりシンプルですね。
また、Piniaではstoreをそのまま使えるので、mapState系は基本いりません。
まとめ
今回は、Vueの状態管理ライブラリである Vuex と Pinia の特徴や使い方、比較ポイントを紹介しました。
Vue公式のシンプルstoreでも動きますが、大規模サービス開発では、Vuex / Piniaが必須です。
新規開発(Vue3系)では、Piniaが公式推奨で、コード量もシンプルなので、Pinia一択かなと思います。
既存プロジェクトでVue2 → Vue3に移行予定がある場合は、Vuexのままでもいいですが、公式サイトに移行ガイドもあるので、可能ならPiniaに移行するといいかもです。
これからVueを学ぶなら、Piniaを重点的に学習するのが一番コスパが良いと思います!
個人的には、Vueは公式で状態管理が推奨されており、Reactのように複数のライブラリから選ぶ必要がないところもいいなと思いました。
最後までお読みいただきありがとうございました!