ライフサイクルとデータリクエスト
この章では、Vue3 のライフサイクルフックを学び、適切なタイミングで fetch を使って JSON ファイルからブログデータを読み込みます。
ライフサイクルとは?
すべての Vue コンポーネントは、作成から破棄まで一連の段階を経ます。これらの段階を「ライフサイクル」と呼びます。
Vue は各段階で対応するフック関数を提供しており、特定のタイミングでコードを実行できます。
例えば、コンポーネントの読み込み完了後にデータをリクエストしたい場合は、onMountedこのフックを使用します。
Vue3 ライフサイクルフックの概要
| フック関数 | 実行タイミング | 主な使用シーン |
|---|---|---|
| setup() | コンポーネント初期化時に最も早く実行 | リアクティブデータ、算出プロパティの定義 |
| onBeforeMount | コンポーネントが DOM にマウントされる前 | あまり使用されない |
| onMounted | コンポーネントが DOM にマウントされた後 | ネットワークリクエストの送信、サードパーティライブラリの初期化、DOM 参照の取得 |
| onBeforeUpdate | データが変化し、DOM が更新される前 | あまり使用されない |
| onUpdated | データが変化し、DOM が更新された後 | DOM 更新後の操作 |
| onBeforeUnmount | コンポーネントが破棄される前 | タイマーのクリア、サブスクリプションの解除、イベントリスナーの削除 |
| onUnmounted | コンポーネントが破棄された後 | クリーンアップ作業の確認 |
ブログプロジェクトで最もよく使うのはonMounted——ページ読み込み後に記事データを取得することです。
ライフサイクルフックは
<script setup>の中に記述する必要があり、非同期関数や条件文の中に置くことはできません。Vue は呼び出し順序によって、それらがどのコンポーネントに属するかを判断します。
onMounted の基本的な使い方
例
import { ref, onMounted } from 'vue'
const message = ref('読み込み中...')
// onMounted はコンポーネントがページにマウントされた後に実行される
onMounted(() => {
// この時点で DOM はレンダリング完了済み。安全に DOM を操作したりリクエストを送信したりできる
console.log('コンポーネントがマウントされました。データの読み込みを開始できます')
message.value = 'データの読み込みが完了しました!'
})
</script>
<template>
<p>{{ message }}</p>
</template>
1つのコンポーネントで onMounted を複数回呼び出すことができ、登録した順に実行されます。
fetch で JSON データを読み込む
記事データを JSON ファイルに置き、onMounted 内で fetch を使って読み込むようにします。
ステップ1:データファイルを作成する
例
[
{
"id": 1,
"title": "Vue3 入門完全ガイド",
"summary": "ゼロから Vue3 コンポジション API を学び、ref、reactive、computed などの核心概念をカバーします。",
"content": "<h2>なぜ Vue3 を学ぶのか?</h2><p>Vue3 は現在最も人気のあるフロントエンドフレームワークの1つです...</p>",
"category": "Vue",
"date": "2024-05-10"
},
{
"id": 2,
"title": "JavaScript 非同期プログラミング詳解",
"summary": "Promise、async/await、イベントループとマイクロタスクキューを1つの記事で徹底解説。",
"content": "<h2>非同期とは?</h2><p>JS はシングルスレッドです...</p>",
"category": "JavaScript",
"date": "2024-05-08"
},
{
"id": 3,
"title": "CSS Grid レイアウト実践",
"summary": "CSS Grid で複雑なレスポンシブレイアウトを簡単に実現。",
"content": "<h2>Grid 入門</h2><p>Grid は2次元レイアウトシステムです...</p>",
"category": "CSS",
"date": "2024-05-05"
}
]
JSON ファイルは
public/ディレクトリに置きます。Vite は静的ファイルとして直接提供します。ブラウザでは/posts.jsonで直接アクセスできます。public に置かれたファイルはコンパイルされず、そのままビルド成果物にコピーされます。
ステップ2:コンポーネント内で fetch する
例
<script setup>
import { ref, onMounted } from 'vue'
import BlogCard from '../components/BlogCard.vue'
import CategoryFilter from '../components/CategoryFilter.vue'
const articles = ref([]) // 初期状態は空配列
const isLoading = ref(true) // 読み込み状態
const error = ref(null) // エラー情報
// 記事データを非同期で読み込む
async function fetchPosts() {
isLoading.value = true
error.value = null
try {
const response = await fetch('/posts.json')
// レスポンスが成功したか確認(404 や 500 の場合はエラーをスロー)
if (!response.ok) {
throw new Error(`読み込み失敗:HTTP ${response.status}`)
}
const data = await response.json()
articles.value = data
} catch (err) {
error.value = err.message
console.error('記事データの読み込みに失敗しました:', err)
} finally {
isLoading.value = false
}
}
// コンポーネントのマウント後にすぐデータを読み込む
onMounted(() => {
fetchPosts()
})
</script>
<template>
<div class="home">
<h2>最新記事</h2>
<!-- 読み込み中状態 -->
<p v-if="isLoading" class="status-msg">読み込み中です。お待ちください...</p>
<!-- エラー状態 -->
<p v-else-if="error" class="status-msg error">
読み込み失敗:{{ error }}
<button @click="fetchPosts">再試行</button>
</p>
<!-- 空データ状態 -->
<p v-else-if="articles.length === 0" class="status-msg">
まだ記事がありません。お楽しみに。
</p>
<!-- 通常データ表示 -->
<template v-else>
<div class="article-grid">
<BlogCard
v-for="article in articles"
:key="article.id"
:id="article.id"
:title="article.title"
:summary="article.summary"
:date="article.date"
:category="article.category"
/>
</div>
</template>
</div>
</template>
<style scoped>
.status-msg {
text-align: center;
padding: 60px 0;
font-size: 16px;
color: #999;
}
.error { color: #e74c3c; }
.error button {
margin-left: 10px;
padding: 4px 12px;
cursor: pointer;
}
</style>
非同期リクエストの3つの状態を処理する
データリクエストは必ず次の3つの UI 状態をカバーすべきです。そうしないとユーザー体験が大きく損なわれます:
| 状態 | 発生タイミング | UI の表示 |
|---|---|---|
| 読み込み中 | リクエスト送信後、レスポンスが返る前 | ローディングアニメーション、スケルトンスクリーン |
| 成功 | データが正常に返る | コンテンツを正常にレンダリング |
| 失敗 | ネットワークエラー、サーバー異常 | エラーメッセージ + 再試行ボタン |
さらに4つ目の状態も処理できます——空データ(リクエストは成功したが空配列が返る場合)。空白ページではなく、ユーザーに明確なメッセージを表示します。
やってみよう:詳細ページも fetch で読み込むように変更する
詳細ページでは、URL の ID に基づいて JSON から対応する記事を探す必要があります。
データ読み込みロジックを再利用できます——詳細ページでも fetch を呼び出し、フィルタリングします。
例
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useRoute, RouterLink } from 'vue-router'
const route = useRoute()
const id = Number(route.params.id)
const posts = ref([]) // すべての記事
const isLoading = ref(true)
const error = ref(null)
// ID に基づいて現在の記事を検索
const article = computed(() => {
return posts.value.find(p => p.id === id)
})
// すべての記事データを読み込む
async function fetchPosts() {
isLoading.value = true
try {
const res = await fetch('/posts.json')
if (!res.ok) throw new Error('読み込み失敗')
posts.value = await res.json()
} catch (err) {
error.value = err.message
} finally {
isLoading.value = false
}
}
onMounted(() => {
fetchPosts()
})
</script>
<template>
<div class="post-view">
<p v-if="isLoading">読み込み中...</p>
<p v-else-if="error">読み込み失敗:{{ error }}</p>
<div v-else-if="!article">
<h2>記事が存在しません</h2>
<RouterLink to="/">ホームに戻る</RouterLink>
</div>
<article v-else>
<span class="category-tag">{{ article.category }}</span>
<h1>{{ article.title }}</h1>
<time>{{ article.date }}</time>
<div class="content" v-html="article.content"></div>
<RouterLink to="/">← ホームに戻る</RouterLink>
</article>
</div>
</template>
本章のまとめ
この章では、Vue3 ライフサイクルフックの中核である onMounted、fetch を使って public ディレクトリから JSON データを読み込む方法、そしてリクエストの4つの UI 状態(読み込み/成功/失敗/空)の処理方法を学びました。
これでブログデータは外部 JSON ファイルから非同期で読み込まれ、実際のプロジェクトの動作方式により近づきました。
その他の拡張