1. はじめに
これまで状態管理ライブラリとして、Vuexを使用してきましたが、最近Piniaを使用する機会がありました。
Vuexとの違いで躓いた部分や、新たに得た学びがあったので以下にまとめます。
2. Piniaとは
Piniaは、Vueチームによって公式に推奨されている状態管理ライブラリであり、Vuexの後継と位置付けられています。Vue 3のComposition APIとの統合やTypeScript対応を前提に設計されており、以下のような特徴があります。
機能 | Vuex | Pinia |
---|---|---|
Stateの定義 | state |
ref() / reactive()
|
Getter | getters |
computed() |
Action | actions |
任意の関数 |
Mutation | 必須 | 不要 |
TypeScript対応 | 定義が煩雑な傾向 | 型安全かつ補完が効きやすい |
APIスタイル | Options API中心 | Composition / Options 両対応 |
✅ 主なメリット
- Mutationが不要で定義がシンプル
- TypeScriptとの高い親和性
- Store定義の柔軟性(関数ベース)
3. 書き方の違い:Options API vs Composition API
Piniaでは、2種類の定義スタイルがサポートされています。どちらもdefineStore()
を用いますが、TypeScriptとの相性を考慮するとComposition APIスタイルが推奨される場面が多くなります。
✔ Options APIによる定義
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
getters: {
double: (state) => state.count * 2
},
actions: {
increment() {
this.count++
}
}
})
✔ Composition APIによる定義
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const double = computed(() => count.value * 2)
const increment = () => {
count.value++
}
return { count, double, increment }
})
Composition APIスタイルでは、返却するプロパティに対して型推論が効くため、より堅牢な型定義が可能です。
4. 実際にPiniaを使用する中で得た学び
Piniaを使ってVue 3 + Composition APIの開発を行う中で、状態管理だけでなくAPI通信やリアクティブ性の扱いにおいても重要な学びがありました。
ここでは特に印象的だった2つの点を紹介します。
4-1. Pinia Coladaの useQuery()
とは?
Pinia Coladaは、Piniaでの状態管理と非同期データ取得の設計を統合的に扱えるようにするユーティリティライブラリです。内部的には TanStack Query というデータフェッチライブラリをベースにしていますが、defineQueryOptions()
+ useQuery()
によって、クエリの定義と実行をより型安全かつ簡潔に書けるようになっています。
参考:
使用例:
import { defineQueryOptions, useQuery } from '@pinia/colada'
import { fetchUser } from '@/api'
// クエリの定義:引数でユーザーIDを受け取り、データを取得
const userQuery = defineQueryOptions((id: number) => ({
key: ['user', id],
query: () => fetchUser(id),
}))
// useQueryを使ってクエリを実行(引数は直接値)
const { data: user } = useQuery(userQuery, 1)
// 引数を関数として渡すことで動的に実行も可能
const { data: dynamicUser } = useQuery(userQuery, () => props.userId)
このように、Pinia Colada の useQuery() を使うことで、クエリの定義・実行・状態取得の責務を自然に分離しつつ、PiniaのComposition APIストアと統合可能になります。
CompositionAPIにおけるuseQuery()
のfetchタイミング
以下のようなコードでは、onMounted()
を使用しなくてもAPIリクエストが自動で発行されます。
setup() {
const { data, isLoading } = useQuery(['user'], fetchUser)
}
なぜonMountedが不要なのか?
-
useQuery()
はsetup()
実行時に初期化されるため、onMounted()
を使わなくても初回描画前に自動でAPIリクエストが開始される。なお、この挙動は enabled オプションで制御可能 -
Vueのライフサイクルにおいて、
setup()
はonBeforeMount()
よりも前に実行される
つまり、画面の初回描画よりも前にAPIリクエストが開始されるのが正確な挙動です。これは事前フェッチやSSRの最適化にも有効です。
SSR(サーバーサイドレンダリング)とは、サーバー上でHTMLを生成し、それをブラウザに送る仕組み
https://ja.vuejs.org/guide/scaling-up/ssr
4-2. storeToRefs()
の活用とリアクティブ性保持
PiniaでComposition APIスタイルを用いてストアを定義した場合、戻り値のオブジェクトはVueのref
やcomputed
で構成されており、分割代入によってリアクティブ性が失われる可能性があります。
const counter = useCounterStore()
const { count, double } = counter // ⚠ この方法は非推奨
これは、Piniaのstore
オブジェクトがProxyラップされているため、直接分割代入するとVueのリアクティブシステムから外れてしまうためです。
解決方法:storeToRefs()
を使用
import { storeToRefs } from 'pinia'
const counter = useCounterStore()
const { count, double } = storeToRefs(counter) // ✅ リアクティブ性が維持される
storeToRefs()
は、storeから取得したref
やcomputed
を適切に分解し、Vueコンポーネント内でもリアクティブに扱えるように変換してくれるヘルパー関数です。
5. まとめ
今まで使用したことのないライブラリを使用するときは、これまで使っていたライブラリの使い方を前提として考えてしまうことのないよう、違いを理解しておく必要があると感じました。
推奨される新しい技術に適応し、知識も、開発するプロダクトも継続的にアップデートしていきたいです。