はじめに
この記事では、Vue 3 の状態管理のデファクトスタンダードである Pinia を導入します。 コンポーネントに直書きしていたデータを分離し、ポートフォリオのコードをスッキリ整理するために、より Vue 3 らしい Setup Store 形式で実装した手順をメモとしてまとめます。
現在、本業のプロジェクトではVue.jsをメインで使用していますが、個人開発ではReactを中心に技術習得をしています。
ポートフォリオ作成をするにあたり、Vue.jsで構築しようと思った理由は以下の2つです。
- 実務スキルの深掘り:本業で使用しているVue.jsのエコシステムを体系的に学び直し、現場での実務スピードと品質を向上させたいため
- 技術の相対比:ReactとVueの設計思想の違い(Hooks vs Composablesなど)を実際に使って試すことで、状況に応じた最適な技術選定ができるエンジニアを目指すため
1. Piniaのインストール
まずはプロジェクトにPiniaを追加します。
npm install pinia
2. アプリへの登録(main.ts)
Vueアプリのインスタンスに対してPiniaを登録します。
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.mount('#app')
3. ストアの作成(Setup Store形式)
Piniaには「Option Store」と「Setup Store」の2通りの書き方がありますが、
今回はComposition APIと同じ感覚で書けるSetup Storeを採用しました。
ストアファイルは、一般的に src/stores/ フォルダ内に作成します。
今回はプロフィール情報を管理する想定で profile.ts という名前で作成します。
ref が State、computed が Gettes、function が Actionsに対応します。
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useProfileStore = defineStore('profile', () => {
// State
const headline = ref('メインタイトル')
const subtext = ref('ここにサブテキストを入力します。\n改行も保持されます。')
const socialLinks = ref([
{ url: 'https://github.com/username', label: 'GitHub', iconPath: '/icons/github.png' },
{ url: '#', label: 'X', iconPath: '/icons/x.svg' },
{ url: '#', label: 'Instagram', iconPath: '/icons/instagram.svg' },
])
return { headline, subtext, socialLinks }
})
4. コンポーネントでの利用
作成したストアをコンポーネントでインポートして利用します。
v-for を使うことで、テンプレートを動的かつシンプルに保つことができます。
<template>
<section class="hero min-h-screen flex flex-col justify-center items-center text-white">
<div class="text-center px-4">
<h1 class="text-4xl font-bold mb-4">{{ profile.headline }}</h1>
<p class="text-lg mb-8 whitespace-pre-wrap">{{ profile.subtext }}</p>
<div class="flex justify-center gap-4 mt-2">
<SocialIcon
v-for="link in profile.socialLinks"
:key="link.label"
:url="link.url"
:label="link.label"
:iconPath="link.iconPath"
/>
</div>
</div>
</section>
</template>
<script setup lang="ts">
import { useProfileStore } from '../stores/profile'
import SocialIcon from './SocialIcon.vue'
const profile = useProfileStore()
</script>
5. Reactと比較してのメリットとは
一般的に、React(ReduxやContext API)と比較した際のPiniaのメリットとして以下の点が挙げられます。
-
ボイラープレートが圧倒的に少ない: ReduxのようにAction TypesやReducerを細かく分ける必要がなく、直感的に記述することができる
-
TypeScriptとの親和性: Setup Store形式だと、特別な設定なしで型推論が強力に効くため、型安全な開発が容易であること
-
関心の分離が容易: コンポーネントに直書きしていたデータをストアに逃がすことで、テンプレートの視認性の向上やロジックの再利用がしやすくなる
まとめ
Piniaを導入することで、ポートフォリオのデータを一括管理できるようになり、保守性が向上しました。
今後は、プラウザをリロードしても状態が消えない「永続化(pinia-plugin-persistedstate)」や、より複雑な非同期処理の管理にも挑戦してみたいと思います。
この記事が少しでも参考になれば幸いです!