【Vue.js】非同期データの取得と表示|AxiosでAPIからJSONを読み込む基本パターン

【Vue.js】非同期データの取得と表示|AxiosでAPIからJSONを読み込む基本パターン Vue.js

画面に表示するデータを、APIサーバーから取得する場面は、Webアプリで最もよくある処理の1つです。VueではHTTPクライアントのAxiosを使うと、少ないコードで書けます。この記事では、Vue 3(<script setup>)を前提に、APIからJSONを取得して表示する基本の形と、実際に必要になる、エラー処理やタイムアウト、中止までを整理します。

スポンサーリンク

準備:プロジェクトの作成とAxiosのインストール

新しく始めるなら、Vueの公式ツールcreate-vue(npm create vue@latest)を使います。Viteをベースにした、現在の標準のプロジェクト構成になります。Vue CLIで作られた古いプロジェクトでも、このあとのコードは同じように使えます。

Shell:プロジェクトの作成とインストール
# プロジェクトを作る(Vite ベースの公式ツール)
npm create vue@latest

# Axios を追加する
npm install axios

基本の取得と表示

onMountedの中でaxios.get()を呼び、結果をrefに入れて、v-forで表示します。取得したJSONは、response.dataに入ります。読み込みの状態はloading、失敗はerrorで持ちます。loadingを最初からtrueにしておくと、取得前に空のリストが一瞬見えることがありません。finallyでfalseに戻すと、成功しても失敗しても、読み込みの終了を扱えます。

Vue:基本のパターン
<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'

const posts = ref([])
const loading = ref(true)      // 最初から「読み込み中」にしておく
const error = ref('')

onMounted(async () => {
  try {
    const response = await axios.get('https://jsonplaceholder.typicode.com/posts')
    posts.value = response.data.slice(0, 10)     // JSON は response.data に入る
  } catch (e) {
    error.value = '取得に失敗しました'
  } finally {
    loading.value = false                         // 成功しても失敗しても、読み込みは終わり
  }
})
</script>

<template>
  <p v-if="loading">読み込み中…</p>
  <p v-else-if="error">エラー:{{ error }}</p>
  <ul v-else>
    <li v-for="post in posts" :key="post.id">{{ post.title }}</li>
  </ul>
</template>

AxiosはHTTPのステータスが2xx以外のとき、自動的にエラー(reject)として扱います。そのため、fetchのように、response.okを自分で確認する必要はありません。fetchとの違いはJSONファイルからデータを取得する方法をご覧ください。リスト表示はv-forで配列やオブジェクトをループして表示する方法、表示の出し分けはv-ifで条件分岐させる方法で解説しています。

共通の設定をまとめる:axios.create

URLの前半(baseURL)やtimeoutのような共通の設定は、axios.createでまとめたインスタンスを作ります。特にtimeoutは重要です。Axiosは、既定ではタイムアウトがなく、サーバーから応答が返らないと、「読み込み中」のまま止まってしまいます。

JavaScript:api/client.js
// api/client.js:共通の設定をまとめた Axios のインスタンス
import axios from 'axios'

export const api = axios.create({
  baseURL: 'https://jsonplaceholder.typicode.com',   // 毎回書かなくてよくなる
  timeout: 10000                                     // 10秒で諦める(既定はタイムアウトなし)
})

エラーの種類を見分ける

Axiosのエラーは、原因によって、持っている情報が違います。大きく3つに分けて扱うと、利用者に伝えるメッセージを変えられます。

  • error.responseがある:サーバーが応答したが、エラーのステータスだった(404、500など)。
  • error.requestだけがある:リクエストは出したが、応答がなかった。ネットワークの断絶、タイムアウト、後述のCORSなど。
  • どちらもない:リクエストを作る前の、設定のミスなど。error.messageを確認する。

取得を中止する:AbortController

取得中に別の画面へ移動すると、古いリクエストの応答が、あとから返ってきて処理されます。AbortControllerのsignalをリクエストに渡しておき、画面を離れるときにabort()すると、中止できます。自分で中止したときは、エラーとして扱わないよう、axios.isCancel(e)で見分けます。連続して取得するときの古い応答の上書きなど、さらに詳しい扱いは非同期APIの状態管理パターンをご覧ください。

完成コード

Vue:エラーの分類と中止に対応した完成版
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
import axios from 'axios'
import { api } from './api/client'

const posts = ref([])
const loading = ref(true)
const error = ref('')

// エラーの種類に応じて、メッセージを変える
function toMessage(e) {
  if (e.response) {
    // サーバーが応答したが、エラーのステータスだった
    return `サーバーがエラーを返しました(${e.response.status})`
  }
  if (e.request) {
    // リクエストは出したが、応答がなかった(ネットワーク断、タイムアウト、CORS など)
    return '通信できませんでした。ネットワークを確認してください。'
  }
  // リクエストを作る前の、設定のミスなど
  return `リクエストに失敗しました:${e.message}`
}

let controller = null

onMounted(async () => {
  controller = new AbortController()
  try {
    const response = await api.get('/posts', { signal: controller.signal })
    posts.value = response.data.slice(0, 10)
  } catch (e) {
    if (axios.isCancel(e)) return      // 自分で中止した場合は、エラー扱いにしない
    error.value = toMessage(e)
  } finally {
    loading.value = false
  }
})

// 画面を離れるときは、取得中のリクエストを中止する
onBeforeUnmount(() => controller?.abort())
</script>

<template>
  <p v-if="loading">読み込み中…</p>
  <p v-else-if="error" role="alert">{{ error }}</p>
  <ul v-else>
    <li v-for="post in posts" :key="post.id">{{ post.title }}</li>
  </ul>
</template>

エラーを画面にどう見せるかの設計はAPIエラーハンドリングの設計例、レスポンスに応じた画面の出し分けはAPIレスポンスに応じたUI分岐処理の書き方が参考になります。状態をまとめたComposableにしたい場合は、非同期APIの状態管理パターンをご覧ください。

つまずきやすい点

CORSのエラー

外部のAPIを呼ぶと、ブラウザのコンソールに「CORS」のエラーが出て、取得できないことがあります。MDNによると、ブラウザは、別のオリジン(ドメイン、スキーム、ポートのどれかが違うもの)へのリクエストを、サーバーがAccess-Control-Allow-Originのヘッダーで許可した場合にだけ許します。失敗した詳しい理由は、セキュリティ上、JavaScriptからは見えず、ブラウザのコンソールでしか確認できません。クライアント側のコードだけでは直せず、APIのサーバー側で許可するか、自分のサーバーを経由して呼び出す必要があります。

APIキーなどの秘密情報を、フロントのコードに書かない

ブラウザに送られるコードは、誰でも読めます。環境変数に入れても、ビルドに埋め込まれるものは、公開されたのと同じです。秘密のキーが必要なAPIは、自分のサーバーを経由して呼び出してください。環境ごとの接続先の切り替えは環境ごとにAPIのURLを切り替える方法をご覧ください。

よくある質問(FAQ)

QfetchではなくAxiosを使うメリットは何ですか?
AJSONがresponse.dataに自動で入る、2xx以外が自動でエラーになる、baseURLやtimeoutなどを共通設定にまとめやすい、といった点です。単純な取得なら、追加のインストールが不要なfetchでも十分です。
Q「読み込み中」のまま、止まってしまいます。
Aサーバーから応答が返っていない可能性があります。Axiosは既定ではタイムアウトがないため、timeoutを指定し、finallyでloadingをfalseに戻してください。
QCORSのエラーは、どうすれば直りますか?
Aクライアント側のコードだけでは直せません。APIのサーバーがAccess-Control-Allow-Originで自分のオリジンを許可するか、自分のサーバーを経由してAPIを呼び出す必要があります。
Q画面を離れたあとに、応答が来るとどうなりますか?
A古い応答が、破棄されたコンポーネントの状態を更新しようとして、無駄な処理や不具合の原因になります。AbortControllerで、画面を離れるときに、リクエストを中止してください。

まとめ

Axiosでの取得は、onMountedとasync/await、loadingとerrorの状態で基本の形になります。実用にするには、timeoutを設定し、エラーの種類を見分け、画面を離れるときには取得を中止します。CORSのエラーと、秘密情報をフロントに置かない点にも注意してください。