はじめに
本連載では、JavaScriptを利用して動的なWebページを構築できるフレームワークVue.jsを、データの型定義ができるように拡張されたAltJS(コンパイルしてJavaScriptにする言語)であるTypeScriptで活用する方法を、順を追って説明しています。
一般にWebページは、ユーザーの入力値やWeb APIから取得したデータなど、さまざまな状態を持ちます。これらの状態がさまざまなコンポーネントに散在すると管理が困難になるため、Webページ全体で単一の状態を共有して管理することが望ましいと言われています。Vue.jsには、このような状態管理を行うライブラリーとしてVuexがありますが、今回紹介するPiniaは、Vuexの代替として利用できる状態管理ライブラリーです。よりシンプルな利用方法や、TypeScriptへのネイティブ対応などのメリットがあります。
本記事では、Vue.jsのWebページでPiniaを利用して状態管理を行う方法を、サンプルコードとともに説明していきます。
対象読者
- これからVue.jsに入門したい方
- Webページ全体の状態管理をシンプルに行いたい方
- Vuexが複雑で難しいと感じていた方
必要な環境
本記事のサンプルコードは、以下の環境で動作を確認しています。
-
Windows 10 64bit版
- Node.js v20.9.0 64bit版
- Vue.js 3.3.7
- Pinia 2.1.7
- Vuex 4.0.2(Piniaとの比較用)
- TypeScript 5.2.2
- Microsoft Edge 119.0.2151.58
サンプルコードを実行するには、サンプルのフォルダーで「npm install」コマンドを実行してライブラリーをダウンロード後、「npm run dev」コマンドを実行して、Webブラウザーで「http://localhost:5173/」を表示します。
Pinia対応のプロジェクトを生成して動作させる
Vue.jsのプロジェクトを生成時に実行する「npm init vue@latest」コマンドでは、状態管理のライブラリーとして(Vuexではなく)Piniaをプロジェクトに含めることができます。対話的に表示される「Add Pinia for state management?」に「Yes」と回答します。本記事ではTypeScriptを利用するので「Add TypeScript?」も「Yes」と回答します。
生成したプロジェクトに含まれるPiniaの実装をチェック
生成されたプロジェクトには、Webページの初期化コードでPiniaを有効にする処理と、状態を格納する「ストア」の実装が含まれます。初期化コード(src/main.ts)はリスト1の通りです。(1)のapp.useメソッドに、createPinia関数の戻り値を渡して実行することで、Vue.jsのWebページでPiniaを利用できるようになります。
const app = createApp(App) // アプリ生成 app.use(createPinia()) // Piniaの使用を指定 ...(1) app.mount('#app') // アプリをマウント(表示)
次に、ストアの実装をリスト2に示します。
export const useCounterStore = defineStore('counter', () => { // ステート ...(1) const count = ref(0) // ゲッター ...(2) const doubleCount = computed(() => count.value * 2) // アクション ...(3) function increment() { count.value++ } // 定義したステート、ゲッター、アクションを返却 ...(4) return { count, doubleCount, increment } })
defineStoreメソッドの第1引数にストアを識別するID、第2引数にストアの内容を返却する関数を指定して、ストアを取得するuseCounterStore関数を定義します。第2引数に指定するストアの内容には表1の内容を定義して、(4)でreturnします。
No. | 項目 | 意味 | リスト2での定義内容 |
---|---|---|---|
(1) | ステート | ストアで管理する状態 | count(カウンターの数値) |
(2) | ゲッター | ステートをもとに返却する値 | doubleCount(countの2倍) |
(3) | アクション | ストアに対して行う操作 | increment(countを1増やす) |
[補足]Setup StoresとOption Stores
リスト2のストア記述方法は「Setup Stores」と呼ばれ、Composition APIのコンポーネントで利用するsetup関数と記述方法が類似しています。ステートはコンポーネントのデータ、ゲッターは算出プロパティ、アクションはメソッドに、それぞれ対応します。
Setup Storesに対して、Options APIのコンポーネント記述法に類似した「Option Stores」を利用することもできます。リスト2と同じストアをOption Storesで記述すると、リスト3の通りになります。実装の詳細はp002-option-storesサンプルを参照してください。
export const useCounterStore = defineStore('counter', { // ステート state() { return { count : 0 } }, // ゲッター getters: { doubleCount: (state) => state.count * 2 }, // アクション actions : { increment() { this.count++ } } })
生成したプロジェクトに実装を追加してPiniaを動作させる
CLIツールで生成させたプロジェクトには、Piniaの初期化処理(リスト1)とストア(リスト2)が含まれますが、ストアを利用する処理は含まれません。そこで、生成したプロジェクトに実装を追加して、Piniaを動作させる図3のサンプルを作っていきます。ストアのcountステートとdoubleCountゲッターの値が画面に表示され、ボタンをクリックするごとにcountが1ずつ増えていき、doubleCountはcountの2倍の値が表示されます。
Webページ全体に対応するAppコンポーネントには、ストアを表示するShowStoreコンポーネントと、状態を更新するMutateStateコンポーネントを含めるようにします(詳細はサンプルコードを参照)。表示と更新でコンポーネントを分けるのは、コンポーネントをまたいで状態が共有されることを示すためです。
ShowStoreコンポーネントの実装はリスト4の通りです。<script>部の(2)で、リスト2のcounter.tsからインポートしたuseCounterStore関数を実行してストアを取得し、<template>部の(1)でストアのcountステートとdoubleCountゲッターを画面に表示します。
<template> <div class="component"> <!-- ストアの内容を表示 ...(1)--> <div>State(count):{{ store.count }}</div> <div>Getter(doubleCount):{{ store.doubleCount }}</div> </div> </template> <script setup lang="ts"> // ストアを取得 ...(2) import { useCounterStore } from '../stores/counter' const store = useCounterStore() </script>
一方、MutateStateコンポーネントの<script>部はリスト5の通りです。(1)でストアを取得し、ボタンクリック時の処理(2)で、ストアのincrementアクションを実行してカウンターを1増やします(3)。
// ストアを取得 ...(1) import { useCounterStore } from '../stores/counter' const store = useCounterStore() // カウンター追加ボタンの処理 ...(2) function onClickButtonIncrement() { // ストアのincrementアクションを実行 ...(3) store.increment() }
ShowStoreコンポーネント(リスト4)とMutateStateコンポーネント(リスト5)は、それぞれ別個にストアを取得していますが、Piniaの仕組みにより、これらのストアは単一の状態を共有するため、MutateStateコンポーネントでincrementアクションを実行すると、ストアのカウンターが増加して、ShowStoreコンポーネント画面表示に反映されます。