PiniaでVueアプリのデータを一括管理

PiniaでVueアプリのデータを一括管理

2022年4月5日



Vue アプリのデータを一括管理

Vue の部品の間でデータをやり取りする方法を、Vue 公式チュートリアルで学習しました。しかし、部品を呼び出す階層が深くなったり、Vue Router などを使う場合には、データの受け渡しが面倒になる場合があります。このような場合には、Vue のアプリ内で一括してデータの管理ができると便利です。この記事では、Vue の公式サイトで推奨されているパッケージ(モジュール)の「Pinia」を紹介します。

Pinia とは?

Pinia は、Vue の「部品」間でデータを共有するためのモジュールです。 同じような機能を提供するモジュールは他にもありますが、2022 年 4 月現在、Vue の公式ページで推奨されている、「ストアライブラリ」です。

(*)日本語の公式サイトでは、従来から利用されてきた「Vuex」が推奨されていますが、英語の Vue のサイトでは、「Pinia」が推奨されています。

Vuex でも Pinia のどちらも Vue で利用可能ですが、初心者には、Pinia の方がシンプルな実装なので簡単に導入できると思います。

Vite のテンプレートを利用して、Vue のサンプルプロジェクトを作成する場合には、プロジェクト作成時にこの「Pinia」を予め組み込む事が可能です。

他の似たような仕組みに React と組み合わせて利用される事の多い「Redux」があります。Vue でも Redux を利用することは可能ですが、使いやすさを考えると、「Pinina」か「Vuex」を利用する方が便利です。

ストアライブラリを利用する利点

Web アプリの色々な部品で利用するデータや、複数の部品を深い階層で呼び出して一つのページを作っている場合などは、Vue の公式チュートリアルで学習した「props」などを使ってやり取りするのが、難しい場合や、面倒になります。この様な場合、「ストアライブラリ」に必要なデータを集約して、必要なデータを必要な部品から呼び出すようにした方が、コードがシンプルになります。

特に、Vue Router を使ってページ毎に表示をする場合は、ページ間でデータをやり取りする方法が限られてしまいます。しかし、アプリ内で共通のデータを利用したい場合などは、1箇所でデータを管理して直接どのページからもアクセスできるようにした方が、実装がシンプルです。

例えば、オンラインショップのサイト(アプリ)を作る場合、商品の分類毎に別のページを用意した方が利用者には便利です。例えば、洋服のページと靴のページは分けておいた方が便利です。こうしたサイトでは購入予定の商品をカートに入れておいて、最後に支払いをする様に作るのが一般的です。このカートに入っている商品の情報は、サイト全部で共有した方が便利です。商品の分類毎に支払いをしなければいけない作り方をすると、利用者には不便だからです。

そこで、各ページでカートに入れる場合には、カートの中身の商品は、一括管理して、どのページからもカートに商品を入れることができて、中身を見ることができる様に作成します。この様な場合に、「Pinia」などの「ストアライブラリ」を使います。

サンプルプロジェクトの例

Vite で Vue のテンプレートを使ってサンプルプロジェクトを作成する場合、この連載でも何回か紹介していますが、以下のコマンドでプロジェクトを作成します。

$ npm init vue

実行すると、プロジェクト名などが聞かれますが、その中で以下の設問があります。

✔ Add Pinia for state management? … No / Yes

この設問で「Yes」を選択すると、「Pinia」が自動的に組み込まれます。

この場合、作成されたサンプルプロジェクトで、「pinia」を利用する準備ができています。

src / main.js;
import { createApp } from "vue";
import { createPinia } from "pinia";

import App from "./App.vue";
import router from "./router";

const app = createApp(App);

app.use(createPinia());
app.use(router);

app.mount("#app");

これで、「Pinia」を利用できるようになっています。

一括管理するデータの例として、「src/store/conter.js」が作成されていて、「ストア」の例が作成されています。

回数をカウントするためのデータです。

「defineStore」という「pinia」のパッケージで提供される関数を呼び出すして、データの詳しい設定を行なっています。データは、「counter」という変数に数字を覚えるようになっています。 「actions」は、この「ストア」で扱うデータの処理する関数を記述します。「getters」は「ストア」で扱うデータを使って何か加工(処理)したデータが必要な場合に利用します。

この例では、「actions」は、1づつカウンタの値を増やす処理「increment」、「getters」は、ダブルカウント(2回ずつカウントした場合の値)を計算して返す「doubleCount」が実装されています。

//(src/store/counter.js)
import { defineStore } from "pinia";

export const useCounterStore = defineStore({
  id: "counter",
  state: () => ({
    counter: 0,
  }),
  getters: {
    doubleCount: (state) => state.counter * 2,
  },
  actions: {
    increment() {
      this.counter++;
    },
  },
});

サンプルプロジェクトでは、このデータを利用していませんが、利用する場合には、以下のようにします。 「AboutView.vue」に組み込んでみました。

<template>
  <div class="about">
    <h1>This is an about page</h1>
    <button @click="count.increment">
      {{ count.counter }}
    </button>
  </div>
</template>
<script>
import { useCounterStore } from "../stores/counter";
export default {
  setup() {
    const count = useCounterStore();
    return {
      count,
    };
  },
};
</script>

Javascript の中で、「data()」の代わりに「setup()」を作って、そこで、「ストア」で管理しているデータにアクセスするための記述を書きます。

import { useCounterStore } from "../stores/counter";

でストアで作成した、データの名前を取り込みます。

setup() {
    const count = useCounterStore();
    return {
      count,
    };
},

で、「count」という名前で、ストアのデータにアクセスできる様にしています。

あとは、HTML の記述では、「data()」や「methods:{}」と同じ様な要領でストアのデータにアクセスできます。

<button @click="count.increment">{{ count.counter }}</button>

ボタンがクリックされた際には、「count.increment」で、ストアの「actions」にある「increment()」を呼び出しています。「{{ count.counter }}」は、ストアで作成したデータ「counter」の値にアクセスして表示しています。

同じ様な記述を「HomeView .vue」に書いておくと、HomeView からもストアのデータにアクセスできます。例えば、カウンターの値を表示するようにできます。この場合でも、「HomeView.vue」と「AboutView.vue」は直接データのやり取りはしていませんが、共通の「ストア」である「counter.js」にアクセスすることで共通のデータを別々の部品で使うことができます。

Pinia を使う場合の注意

Pinina など、「ストアライブラリ」は便利ですが利用する際にはいくつか注意が必要です。

いろいろなアプリで共通して利用しやすい「部品」にするには、「props」を使ってデータをやり取りした方が使いやすい部品になる場合が多くなります。部品間のデータの受け渡しをしない場合には、「ストアライブラリ」を使う事になりますが、部品側からデータを取得するには、「ストア」で使うデータや処理の名前を同じにしないと利用できません。つまり、「ストア」作成に制限が出てくることになります。 「props」などでデータをやり取りする際は、「個別」になるので、データをやり取りする部品間で、部品の使用に合わせてデータを渡せば良いことになります。しかし、ストアを利用する場合、ストアの実装と部品の実装を合わせることと、同じデータを利用するすべての部品で名前を合わせる必要が出てくるので、少し不便になります。

もう一つは、「Pinia」の場合、部品側でもストアのデータを更新する事が可能になっています。「Vuex」の場合は、ストアの中の「mutation」の中の処理以外では更新できない仕組みになっていました。つまり、更新は常にストアの中でしかできない仕組みになっていました。ところが、「Pinia」の場合には、ストア以外での更新が可能になっています。このこと自体は問題ではないのですが、データの更新が間違っているようなケースでは、どこで間違ったデータをセットしているのかを見つける必要がありますが、複数の部品で更新が可能になっているとデバッグするのが大変になるという問題につながります。 そうしたことも考えると、できればストアの中にデータを更新するための処理を全て書いて、その処理を呼び出す様にしてデータの更新の場所を1箇所にした方が良いと言えます。部品側では、ストアのデータは読み出すだけにして、更新はストアの関数を呼び出して行うようにすると、バグが少なく、デバッグしやすいコードになります。

まとめ

この記事では、Vue でデータを一括管理する方法として、「Pinia」を利用する方法を紹介してきました。「Pinia」は Vue でよく利用されてきた、「Vuex」よりシンプルで使いやすくなっていて、英語の公式サイトでは、「Vuex」に代わって推奨されている「ストアライブラリ」になっています。 React でよく利用されている「Redux」と比べても使いやすく、初心者でも余り問題なく利用できます。

部品化が進んで部品を呼び出す階層が深くなっている場合や、Vue Router を利用して、ページの移動が可能なアプリなどでは、データを一括管理した方が実装がシンプルでわかりやすくなります。

まずは、「Pinia」と Vue の公式チュートリアルで学習したデータのやり取りを使ってみて、どのように使い分けていけば良いかを実際に体験しながら考えると良いと思います。

このように、使いやすい基本モジュールが用意されている点で、Vue は初心者にも使いやすいフロントエンドのフレームワークになっています。


Copyright(c) 2017-2021 by Silicon Valley Super Ware, all rights reserved.

コメント

このブログの人気の投稿

ユーザーインターフェースの設計

足し算以外もできるようにする

Reactで表示する文字に色をつける