目次
はじめに
どうも、Web制作の経験をベースに、
現在はエンジニアとして開発領域に取り組んでいる者です。
前回投稿した PokéAPIの記事 で触れた、
「Vuex」と「localStorage」 を、
今回は実際に自作アプリに導入してみました。
本題
ということで、ここからは本題です。
今回はUI / UXも意識し、よりアプリらしいデザインを目指しました。
左右の < > 矢印で画像を切り替えられるようにしたり、
お気に入りボタンをハートアイコンに変更したりと、
より使いやすく、直感的に操作できるように工夫しています。
👉 アプリはこちら
🔗 https://random-dog-one.vercel.app/
Vuexとはなんぞや
今回は Pinia ではなく Vuex を使用しました。
まず、「Vuexとは何か?」ですが、
個人的には 「アプリ内のデータをひとつの場所で管理できるようにするライブラリ」 だと思っています。
通常、コンポーネント間でデータをやり取りするには、props や $emit を使って「子 → 親 → 別の子」と受け渡す必要があります。
コンポーネントが増えるほど、このやり取りが複雑になりがちです。
そこで 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 などと考え方が近いため、
既存の知識を活かしながら状態管理を整理できる点も、Vuexのメリットだと感じています。
書き方の違い
.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を利用した状態管理と、localStorageを利用したデータの永続化を実装しました。
状態管理をVuexにまとめることで、コンポーネントが増えた場合でもデータの流れを整理しやすくなり、今後より機能の多いアプリを実装する際にも活用できそうです。
今後は、ログイン状態の管理など、状態管理がより重要になる機能にも取り入れていきたいと考えています。
また、Composition APIやREST API、BaaSなどについても、実際のアプリ開発を通して取り入れていきたいと思います。
長くなりましたが、最後まで読んでいただき ありがとうございました!