
Vuex/Piniaのステート、ゲッター、ミューテーションの基本:状態管理の核心概念を徹底解説
2025-07-31はじめに
VuexやPiniaを使用した状態管理の核心となる「ステート(State)」「ゲッター(Getters)」「ミューテーション(Mutations)」の3つの概念は、効果的な状態管理を実現するための基礎です。本記事では、これらの概念を詳細に解説し、具体的なコード例と図解を交えてその役割と使用方法を説明します。Vuexを主な例として使用しますが、Piniaとの違いについても随時触れていきます。
ステート(State):アプリケーションの状態を保持する
ステートとは?
ステートはアプリケーション全体の状態を保持するオブジェクトで、単一の信頼できる情報源(Single Source of Truth)として機能します。全てのコンポーネントが参照可能なグローバルなデータストアと考えることができます。

ステートの定義方法(Vuex)
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0,
user: {
id: null,
name: '',
email: ''
},
todos: []
}
})
ステートの定義方法(Pinia)
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
user: null,
todos: []
})
})
コンポーネントでのステートの使用方法
<!-- Vuexを使用したコンポーネント -->
<template>
<div>
<p>カウント: {{ count }}</p>
<p>ユーザー名: {{ user.name }}</p>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
...mapState(['count', 'user'])
// またはオブジェクト形式でマッピング
// ...mapState({
// count: state => state.count,
// userName: state => state.user.name
// })
}
}
</script>
<!-- Piniaを使用したコンポーネント -->
<template>
<div>
<p>カウント: {{ counter.count }}</p>
<p>ユーザー名: {{ counter.user?.name }}</p>
</div>
</template>
<script setup>
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
</script>
ステートの特徴
- リアクティブ: Vueのリアクティブシステムと統合されており、状態が変更されると自動的にビューが更新される
- 直接変更不可(Vuex): Vuexではミューテーションを介さずに直接ステートを変更すべきではない
- 直接変更可能(Pinia): Piniaではストア内で直接ステートを変更できる(ただし、可能な場合はアクションを使用することが推奨される)
ゲッター(Getters):ステートから派生した値を計算
ゲッターとは?
ゲッターはステートから派生した値を計算するための関数で、コンポーネントのcomputedプロパティに似ています。同じ計算ロジックが複数のコンポーネントで必要な場合や、複雑な状態のフィルタリング・変換が必要な場合に特に有用です。

ゲッターの定義方法(Vuex)
export default new Vuex.Store({
state: {
todos: [
{ id: 1, text: 'Vue.jsを学ぶ', done: true },
{ id: 2, text: 'Vuexを理解する', done: false },
{ id: 3, text: 'Piniaを試す', done: false }
]
},
getters: {
// 完了したTodoのみを返す
doneTodos: state => {
return state.todos.filter(todo => todo.done)
},
// 特定のIDのTodoを取得
getTodoById: state => id => {
return state.todos.find(todo => todo.id === id)
},
// 未完了のTodoの数を返す
undoneTodosCount: (state, getters) => {
return state.todos.length - getters.doneTodos.length
}
}
})
ゲッターの定義方法(Pinia)
export const useTodoStore = defineStore('todo', {
state: () => ({
todos: [
{ id: 1, text: 'Vue.jsを学ぶ', done: true },
{ id: 2, text: 'Vuexを理解する', done: false },
{ id: 3, text: 'Piniaを試す', done: false }
]
}),
getters: {
doneTodos(state) {
return state.todos.filter(todo => todo.done)
},
getTodoById: state => {
return id => state.todos.find(todo => todo.id === id)
},
undoneTodosCount(state) {
return this.todos.length - this.doneTodos.length
}
}
})
コンポーネントでのゲッターの使用方法
<!-- Vuexを使用したコンポーネント -->
<template>
<div>
<h3>完了したタスク</h3>
<ul>
<li v-for="todo in doneTodos" :key="todo.id">
{{ todo.text }}
</li>
</ul>
<p>未完了のタスク数: {{ undoneTodosCount }}</p>
<p>ID2のタスク: {{ getTodo(2).text }}</p>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters([
'doneTodos',
'undoneTodosCount',
'getTodoById'
]),
getTodo() {
return this.getTodoById
}
}
}
</script>
<!-- Piniaを使用したコンポーネント -->
<template>
<div>
<h3>完了したタスク</h3>
<ul>
<li v-for="todo in todoStore.doneTodos" :key="todo.id">
{{ todo.text }}
</li>
</ul>
<p>未完了のタスク数: {{ todoStore.undoneTodosCount }}</p>
<p>ID2のタスク: {{ todoStore.getTodoById(2).text }}</p>
</div>
</template>
<script setup>
import { useTodoStore } from '@/stores/todo'
const todoStore = useTodoStore()
</script>
ゲッターの特徴
- キャッシュされる: 依存するステートが変更されない限り、再計算されない
- 引数を取れる: メソッドスタイルのゲッターを使用することで引数を渡せる
- 他のゲッターを利用可能: ゲッター内で他のゲッターを参照できる
- コンポーネントの計算プロパティとの類似: ロジックの再利用と関心の分離が可能
ミューテーション(Mutations):ステートを変更する唯一の方法
ミューテーションとは?
ミューテーションはVuexにおいてステートを変更する唯一の方法です。各ミューテーションは文字列のタイプ(type)とハンドラ関数を持ち、同期的にステートを変更します。

ミューテーションの定義方法(Vuex)
export default new Vuex.Store({
state: {
count: 0,
user: null
},
mutations: {
// ペイロードなしのミューテーション
increment(state) {
state.count++
},
// ペイロードありのミューテーション
setUser(state, user) {
state.user = user
},
// オブジェクトスタイルのペイロード
updateProfile(state, { name, email }) {
if (state.user) {
state.user.name = name
state.user.email = email
}
}
}
})
ミューテーションの呼び出し方法(Vuex)
ミューテーションは直接呼び出すことはできず、commit
メソッドを使用して呼び出します。
<!-- Vuexを使用したコンポーネント -->
<template>
<div>
<p>カウント: {{ count }}</p>
<button @click="increment">増やす</button>
<button @click="updateUser">ユーザーを更新</button>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
export default {
computed: {
...mapState(['count', 'user'])
},
methods: {
...mapMutations(['increment', 'setUser']),
updateUser() {
this.setUser({ name: '新しいユーザー', email: 'new@example.com' })
// または直接コミット
// this.$store.commit('setUser', { name: '新しいユーザー', email: 'new@example.com' })
}
}
}
</script>
Piniaにおけるミューテーションの代替
Piniaにはミューテーションの概念がなく、アクション(Actions)で直接ステートを変更できますが、Vuexのミューテーションに相当するパターンを実装することは可能です。
// Piniaでのミューテーション的パターン
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
actions: {
// ミューテーション的なアクション
increment() {
this.count++
},
setUser(user) {
this.user = user
}
}
})
ミューテーションの特徴
- 同期的である必要がある: 非同期処理はアクションで行う
- 直接呼び出せない:
commit
を使用して呼び出す - 追跡可能: Vue DevToolsでミューテーションの記録を確認できる
- ペイロードを受け取れる: ミューテーションにデータを渡せる
3つの概念の連携フロー
ステート、ゲッター、ミューテーションの関係を理解するために、典型的なデータフローを見てみましょう。
コンポーネント → (ディスパッチ) → アクション → (コミット) → ミューテーション → (変更) → ステート → (反映) → ゲッター → (更新) → コンポーネント
具体的なフローの例
- ユーザーがボタンをクリック
- コンポーネントがアクションをディスパッチ
- アクションがAPIからデータを取得
- アクションがミューテーションをコミット
- ミューテーションがステートを更新
- ステートの変更がゲッターに反映
- ゲッターの変更がコンポーネントの表示を更新
ベストプラクティス
ステート設計のコツ
- 適切な構造化: 関連するデータはグループ化する
- 正規化: 複雑なデータ関係はデータベースのように正規化
- シリアライズ可能: デバッグのためにシリアライズ可能なデータを保持
ゲッターの活用方法
- 複雑なロジックのカプセル化: コンポーネントから複雑な状態計算を隠蔽
- 再利用: 複数のコンポーネントで同じ計算が必要な場合に使用
- パフォーマンス最適化: 計算量の多い処理をキャッシュ
ミューテーションの設計原則
- 単一責任: 1つのミューテーションは1つの変更だけを行う
- 予測可能: 同じ入力に対して常に同じ結果を返す
- シンプルに: 複雑なロジックはアクションに任せる
まとめ
Vuex/Piniaの状態管理システムの中核をなすステート、ゲッター、ミューテーションの3つの概念について詳しく解説しました。これらの概念を正しく理解し、適切に活用することで、以下のようなメリットが得られます。
- ステート: アプリケーションの「真実の源」として、データを一元的に管理
- ゲッター: 派生状態を効率的に計算し、コンポーネント間でロジックを共有
- ミューテーション: 状態変更を追跡可能な方法で制御(Vuex)
これらの概念を組み合わせることで、大規模なVue.jsアプリケーションでもデータフローを明確に保ち、保守性の高いコードベースを維持できます。VuexとPiniaには違いがありますが、基本的な考え方は共通しています。プロジェクトの要件に合わせて適切なライブラリを選択し、これらの状態管理の基本概念を活用してください。