ライフサイクルとデータリクエスト

この章では、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 の基本的な使い方

例

<script setup>
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:データファイルを作成する

例

// ファイルパス:public/posts.json
[
  {
    "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 する

例

<!-- ファイルパス:src/views/HomeView.vue -->
<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 を呼び出し、フィルタリングします。

例

<!-- ファイルパス:src/views/PostView.vue -->
<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 ファイルから非同期で読み込まれ、実際のプロジェクトの動作方式により近づきました。

その他の拡張