自作アプリでVuexに挑戦!リファクタリングで学ぶ状態管理の基礎+localStorage対応

Published
2025-11-09
Author
MT
Tags

はじめに


どうも、エンジニア歴6ヶ月目 の者です。


前回投稿した Poke API の記事 で触れた今後の課題、

「Vuex」や「ローカルストレージ」 について、

今回は、以前自分が作った Dog API アプリ をリファクタリングしながら、

実際にそれらを使ってみました。


本題


ということで、ここからは本題です。


前回作った Dog API アプリ を、今回は Vuex でリファクタリングしてみました。

あわせて UI / UX も見直し、よりアプリらしいデザイン を意識しています。


左右の < > 矢印で画像を切り替えられるようにしたり、

お気に入りボタンをハートアイコンに変更したりと、

前回よりもアプリらしい見た目になったんじゃないかなと思っています!


👉 アプリはこちら🔗 https://random-dog-one.vercel.app/


Vuexとはなんぞや


いまは Pinia の方が主流だと思いますが、

現場では Vuex が使われているため、今回は Vuex を学びました。


まず、「Vuex とは何か?」ですが、

個人的には 「アプリ内のデータをひとつの場所で管理できるようにするライブラリ」 だと思っています。


通常、コンポーネント間でデータをやり取りするには、

props や $emit を使って「子 → 親 → 別の子」と受け渡す必要があります。

コンポーネントが増えるほど、このやり取りが複雑になりがちです。


私の前回の Dog API アプリ でもこの方法でお気に入り機能を作っていましたが、

ページをまたぐとデータ管理が煩雑になってしまいました。


そこで Vuex を使うと、

「子コンポーネント → Vuex(store) → 他のコンポーネント」

という形で、どこからでも同じデータを参照・更新できるようになります。


ここからは、実際のコードを使って props の場合 と Vuex の場合 を比較していきたいと思います。


PropsとVuexの比較


Propsの場合

まずは、props を使って「お気に入りボタンをクリックしたときに、その値をお気に入りページへ渡す」場合の例です。


子コンポーネント側

クリックすると、親コンポーネントにイベントを通知します。

<button @click="emitFavorite">
  ★ Favorite
</button>

<script>
export default {
  methods: {
    emitFavorite() {
      if (!this.dogImg) return;
      this.isDone = true;
      this.$emit('favorite', {
        id: this.dogImg,
        url: this.dogImg,
      });
    },
  },
};
</script>


親コンポーネント側

子コンポーネントから受け取ったデータを配列に push して管理します。

さらに、:dogs="dogArray" のように props を使ってデータを渡します。

<router-view
  @favorite="favorite"
  @delete-img-id="deleteImgId"
  :dogs="dogArray"
/>

<script>
export default {
  data() {
    return {
      dogArray: [],
    };
  },
  methods: {
    favorite(e) {
      this.dogArray.push(e);
    },
  },
};
</script>


受け取ったデータを描画するコンポーネント側

ここでようやく、受け取った dogs 配列を v-for で描画します。

<li v-for="dog in dogs" :key="dog.id" class="gallery-item">
  <div class="img-wrapper">
    <img :src="dog.url" alt="" loading="lazy" />
    <button class="delete-btn" @click="deleteImg(dog.id)">
      <i class="fa-solid fa-trash"></i>
    </button>
  </div>
</li>

<script>
export default {
  props: {
    dogs: {
      type: Array,
      default: () => [],
    },
  },
};
</script>


このように、コンポーネントが増えていくと、

データの受け渡しがどんどん複雑になっていきます。


しかし、Vuex を使えば、このやり取りをひとつの「共有ストア」で管理できるため、

データの流れをぐっとシンプルにすることができます。


Vuexの場合


子コンポーネント側

クリックすると、Vuex(store)にイベントを通知します。

<button @click="emitFavorite">
  ★ Favorite
</button>

<script>
export default {
  methods: {
    emitFavorite() {
      if (!this.currentImg) return;
      this.isDone = true;

      this.$store.commit('favoriteDog', {
        id: Date.now() + Math.floor(Math.random() * 100),
        url: this.currentImg,
      });
    },
  },
};
</script>


Store側

ストアでは、受け取ったデータを state に追加して管理します。

export default new Vuex.Store({
  state: {
    dogs: [],
  },
  mutations: {
    favoriteDog(state, addDog) {
      state.dogs.push(addDog);
    },
  },
});


別ファイルでの描画

最後に、別のコンポーネントで store のデータを参照して、v-for で描画します。

<li v-for="dog in storeDogs" :key="dog.id" class="gallery-item">
  <div class="img-wrapper">
    <img :src="dog.url" alt="" loading="lazy" />
    <button class="delete-btn" @click="deleteImg(dog.id)">
      <i class="fa-solid fa-trash"></i>
    </button>
  </div>
</li>

<script>
export default {
  computed: {
    storeDogs() {
      return this.$store.state.dogs;
    },
  },
};
</script>


このように、Vuex を使えば、props や $emit を介さなくても、

どのコンポーネントからでも同じデータを扱えるようになりました!


さらに、書き方の根本は $emit などと似ているため、

感覚的に使いやすく、とても便利だと感じました。


実際に使ってみると、「規模が大きいアプリでは必須だなあ」と実感しています。


書き方の違い


.vue ファイルでは、data() の中に プロパティを定義 し、処理は methods に書く ことが多いと思います。

一方、Vuex では データを state に置きデータの変更は mutations で行います。


他にも getters や actions などの機能がありますが、今回はシンプルに state と mutations のみを使用しました。


この 「mutations」 は、.vue ファイルでいうところの methods に近い感覚 だと思っています。

また、書き方の面でも $emit や $router と似ていて、$store を使い、$store.state.xxx のように記述することで Vuex 内の値を参照 できます。


一方、mutations 側では関数を定義するときに (state, payload) のように、第一引数に state を書くのがお決まり で、

この state にアクセスすることで、Vuex 内のデータを直接操作 できました。


ローカルストレージ


今回は、お気に入りデータをローカルストレージにも保存できるようにしています。


ライブラリ vuex-persist を使いました。

インストールはとても簡単で、以下のように実行しました

yarn add vuex-persist


あとは、Vuex ストアに少しコードを追加するだけでOKです。

import { VuexPersistence } from 'vuex-persist'

Vue.use(Vuex)

const vuexPersist = new VuexPersistence({
  storage: localStorage,
})

export default new Vuex.Store({

  mutations: {
    RESTORE_MUTATION: vuexPersist.RESTORE_MUTATION,
  },
  plugins: [vuexPersist.plugin]
})


これで、Vuex に保存されたデータが自動的にローカルストレージにも保存されます。

とても便利ですが、今後の課題としては、ライブラリを使わずに手書きでローカルストレージを実装できるようになりたいと思います。


まとめ


今回は Vuex について学び、ローカルストレージ にも挑戦してみました。

Vuex を学んだことで、今後より多機能なアプリを作るときにも、

迷わずに実装を進められるようになったんじゃないかと思います。


まだまだ使いこなせてはいませんが、

今後は ログイン認証など、Vuex の状態管理がより活きる場面 でも使っていけるようにしたいです。


また、そろそろComposition API や REST API、BaaS など、フロント以外の領域についても少しずつ理解を深めていく時期かなと思っています。


長くなりましたが、最後まで読んでいただき ありがとうございました!