採点されません。匿名です。 間違えることは学習の一部です。読むだけでもかまいません。

L08 ch04 — fetch で外部 API

テクノロジ系 / L08 vue-basic — Vue 3 入門

L08 ch04 — fetch で外部 API

ブラウザ標準の fetch API で連携

ゴール

  • fetch で GET / POST
  • async / await
  • onMounted で起動時取得

GET (最小)

<script setup>
import { ref, onMounted } from 'vue'

const items = ref([])

async function loadItems() {
  const res = await fetch('https://jsonplaceholder.typicode.com/todos')
  items.value = await res.json()
}

onMounted(loadItems)
</script>

<template>
  <p>件数: {{ items.length }}</p>
  <ul>
    <li v-for="item in items.slice(0, 5)" :key="item.id">
      {{ item.title }}
    </li>
  </ul>
</template>

エラー処理

async function loadItems() {
  try {
    const res = await fetch('http://localhost:8000/tasks')
    if (!res.ok) {           // ← 必須 (fetch は 404 でも throw しない)
      throw new Error(`HTTP ${res.status}`)
    }
    items.value = await res.json()
  } catch (e) {
    console.error('取得失敗:', e)
    items.value = []
  }
}

POST

async function addTask(title) {
  await fetch('http://localhost:8000/tasks', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ title }),
  })
  await loadTasks()  // 再取得で更新
}

→ headers の Content-Type を忘れると 422

onMounted

コンポーネントが画面に表示された 直後 に実行。

データ取得を 1 度だけしたいときに使う。

<script setup>
import { onMounted } from 'vue'

onMounted(() => {
  console.log('マウント完了')
})
</script>

  • fetch は 404 でも例外を投げない → if (!res.ok) 判定必須
  • CORS エラー → サーバー側で CORSMiddleware 設定 (L08 ch06 で扱う)
  • ネットワーク遅延 → ローディング表示

URL にクエリパラメータを埋める

検索のように動的な値を URL に乗せたいときは テンプレートリテラル が定石。

const keyword = ref('')

async function search() {
  const url = `http://localhost:8000/search?q=${encodeURIComponent(keyword.value)}`
  const res = await fetch(url)
  results.value = await res.json()
}
  • バッククォート ` ` で囲み、${...}` で値を埋め込む
  • 空白・日本語・記号は encodeURIComponent() でエスケープする (+& で URL が壊れる事故を防ぐ)
  • 複数キーなら ?q=...&page=2 のように & で繋ぐ感覚

ボタンクリックで fetch を発火

onMounted は「画面表示と同時に 1 回」だが、検索・送信は ユーザー操作起点 で動かしたい。

このときは @click にハンドラを直接渡すパターンを使う。

<script setup>
const keyword = ref('')
const results = ref([])

async function search() {
  const url = `http://localhost:8000/search?q=${encodeURIComponent(keyword.value)}`
  const res = await fetch(url)
  results.value = await res.json()
}
</script>

<template>
  <input v-model="keyword" placeholder="検索ワード" />
  <button @click="search">検索</button>
</template>
  • onMounted = 起動時 1 回
  • @click = ユーザー操作の度に発火
  • インラインに書くなら @click="async () => { await search() }" の形でも OK

結果の有無で表示を分ける

検索後は「件数 0」と「件数 > 0」で見せ方を変えるのが UX の基本。

v-if / v-else で分岐できる。

<template>
  <ul v-if="results.length > 0">
    <li v-for="r in results" :key="r.id">{{ r.title }}</li>
  </ul>
  <p v-else>該当する結果はありません</p>
</template>
  • v-if で「真のときに描画」
  • v-else直前の v-if と同じ階層 に書く (間に別要素を挟まない)
  • 3 分岐以上なら v-else-if で中間条件を足す感覚

drill

drill/ — fill-fetch

ハンズオン

ch04-fetch 演習ガイド

受講生向け演習問題集

採点の仕組み (読んでから始めてください)

採点テストは 「お題の通りに動くか」 を機械的にチェックします。

ドリルごとに 3つのタイプ があるので、各ドリル冒頭の 【タイプ】 を確認してください。

タイプ fill (穴埋め問題) — 文法・APIを知っているか

  • ___ (3個並んだアンダースコア) の空欄を埋める
  • 既存の <template> のテキストやボタン順は そのまま
  • ロジック自由度: 低 (お題どおりに直す)

タイプ add (機能追加問題) — 既存コードに機能を足せるか

  • 既存コードを残したまま、新しい機能を 追加 する
  • 実装方法は自由 (関数名・変数名・内部ロジックは好きに)
  • ただしテストが要求する見た目 (表示テキスト・ボタンの並び順) は守る
  • 既存テストを壊さないこと

タイプ build (ゼロベース実装問題) — 仕様から実装を組み立てられるか

  • ほぼ空の App.vue から 自力で全部書く
  • <template> 構造・id/class・内部ロジック: 完全に自由
  • 守るのは画面の機能仕様だけ: 表示するテキスト・ボタンの操作で何が起きるか
  • 一番ハードだが、実装力が一番つくタイプ

3タイプ共通で守ること

  • ファイル名 (App.vue) は変えない
  • tests/ フォルダは触らない (採点ロジック)
  • テンプレートの 表示テキスト (「カウント:」等) はテストが見ているので残す

このガイドのドリル一覧

#ドリル名タイトルタイプ難易度
101-fill-fetchfetch + onMounted + async/await - API データ取得🔧 穴埋め
202-build-search-appfetch + クエリパラメータ + v-if/v-else - 検索アプリを白紙から🛠️ ゼロ実装★★

> 詰まったら ヒント ページを開く。それでも分からなければ講師に質問。

ドリル 1: fetch + onMounted + async/await - API データ取得

ドリル名: 01-fill-fetch

> 🔧 【タイプ: 穴埋め問題】 ___ の部分を埋めて完成させてください。既存のコード構造は変えないこと。

問題

___ を埋めて、画面表示時に JSONPlaceholder の todos を取得して一覧表示するアプリを完成させよ。___ は全部で5箇所ある。

<script setup>
import { ref, ___ } from 'vue'             <!-- ___1: 画面表示時に1回実行するライフサイクル関数 -->
const items = ref([])

___ function loadItems() {                  <!-- ___2: 非同期関数を宣言するキーワード -->
  const res = ___ fetch('https://jsonplaceholder.typicode.com/todos')  <!-- ___3: 非同期処理の結果を待つキーワード -->
  items.value = ___ res.json()              <!-- ___4: 同じく結果を待つ -->
}

___(loadItems)                              <!-- ___5: ___1 で import した関数を呼ぶ -->
</script>

<template>
  <p>件数: {{ items.length }}</p>
  <ul>
    <li v-for="item in items" :key="item.id">{{ item.title }}</li>
  </ul>
</template>

確認方法

  1. App.vue に貼り付けて npm run dev で起動
  2. ページを開くと自動で 200 件のリストが表示される
  3. F12 → Network タブで todos リクエストが見える

ドリル 1: ヒント

ドリル名: 01-fill-fetch

> ⚠️ 自力で考えてから読んでください

  • ___1 / ___5: 画面表示時に 1 回実行する Vue のライフサイクル関数(on から始まる)
  • ___2: 非同期関数の宣言キーワード(4 文字)
  • ___3 / ___4: Promise の解決を待つキーワード(5 文字)

ドリル 2: fetch + クエリパラメータ + v-if/v-else - 検索アプリを白紙から

ドリル名: 02-build-search-app

> 🛠️ 【タイプ: ゼロベース実装問題】 ほぼ空の App.vue から仕様を読んで自力で実装してください。<template> 構造・id/class・内部ロジックは完全に自由。ただしテストが見る 画面の表示テキスト・ボタンの動作 は仕様どおりに揃えること。

問題

Vue で「キーワード検索 → 結果表示」のミニアプリを App.vue白紙から 実装してください。

仕様

API はモック (テストで fetch を stub):

GET http://localhost:8000/search?q=<keyword>
→ [{ "id": 1, "title": "..." }, ...]

画面要件

  • 検索キーワード入力欄 (テキスト)
  • 「検索」ボタン クリックで fetch → 結果一覧を表示
  • 結果が空なら 「該当なし」 と表示
  • ローディング中は 「検索中...」 と表示 (任意、テストはここを必須にしない)

守る (テストが見ている)

  • 「検索」ボタンが画面にある
  • 「検索」クリックで fetch が呼ばれる (URL に /search を含む)
  • 入力したキーワードが URL の q= パラメータに反映される
  • 取得した結果のタイトルが画面に表示される
  • 結果が空のとき「該当なし」と表示

このドリルで学ぶこと

学習要素学習資料との対応
テンプレートリテラルでクエリパラメータ付き URL を組み立てるch04 slide「URL にクエリパラメータを埋める」
encodeURIComponent で値を安全にエスケープch04 slide「URL にクエリパラメータを埋める」
ボタンクリックで fetch を発火 (onMounted との対比)ch04 slide「ボタンクリックで fetch を発火」
v-if / v-else で結果の有無を分岐表示ch04 slide「結果の有無で表示を分ける」

このドリルは 「非同期処理 + 分岐UI」の最小形。実務でほぼ必ず出る検索画面パターンを体得します。

ドリル 2: ヒント

ドリル名: 02-build-search-app

> ⚠️ 自力で考えてから読んでください

段階1: 入力欄と結果配列

import { ref } from 'vue'
const keyword = ref('')
const results = ref([])

段階2: 検索ボタンと fetch

async function search() {
  const url = `http://localhost:8000/search?q=${keyword.value}`
  const res = await fetch(url)
  results.value = await res.json()
}

段階3: 結果表示と「該当なし」

<ul v-if="results.length > 0">
  <li v-for="item in results" :key="item.id">{{ item.title }}</li>
</ul>
<p v-else>該当なし</p>

ただし「最初から該当なしと出る」のは違和感あるので searched フラグで「一度でも検索したか」を持つと自然。

考えてみよう

既定では正解・不正解の判定はしません。自分のペースで「答えを見る」を。採点してほしい場合だけ「採点してほしい」を押してください。

ch04-fetch 演習ガイド

受講生向け演習問題集

採点の仕組み (読んでから始めてください)

採点テストは 「お題の通りに動くか」 を機械的にチェックします。

ドリルごとに 3つのタイプ があるので、各ドリル冒頭の 【タイプ】 を確認してください。

タイプ fill (穴埋め問題) — 文法・APIを知っているか

  • ___ (3個並んだアンダースコア) の空欄を埋める
  • 既存の <template> のテキストやボタン順は そのまま
  • ロジック自由度: 低 (お題どおりに直す)

タイプ add (機能追加問題) — 既存コードに機能を足せるか

  • 既存コードを残したまま、新しい機能を 追加 する
  • 実装方法は自由 (関数名・変数名・内部ロジックは好きに)
  • ただしテストが要求する見た目 (表示テキスト・ボタンの並び順) は守る
  • 既存テストを壊さないこと

タイプ build (ゼロベース実装問題) — 仕様から実装を組み立てられるか

  • ほぼ空の App.vue から 自力で全部書く
  • <template> 構造・id/class・内部ロジック: 完全に自由
  • 守るのは画面の機能仕様だけ: 表示するテキスト・ボタンの操作で何が起きるか
  • 一番ハードだが、実装力が一番つくタイプ

3タイプ共通で守ること

  • ファイル名 (App.vue) は変えない
  • tests/ フォルダは触らない (採点ロジック)
  • テンプレートの 表示テキスト (「カウント:」等) はテストが見ているので残す

このガイドのドリル一覧

#ドリル名タイトルタイプ難易度
101-fill-fetchfetch + onMounted + async/await - API データ取得🔧 穴埋め
202-build-search-appfetch + クエリパラメータ + v-if/v-else - 検索アプリを白紙から🛠️ ゼロ実装★★

> 詰まったら ヒント ページを開く。それでも分からなければ講師に質問。

ドリル 1: fetch + onMounted + async/await - API データ取得

ドリル名: 01-fill-fetch

> 🔧 【タイプ: 穴埋め問題】 ___ の部分を埋めて完成させてください。既存のコード構造は変えないこと。

問題

___ を埋めて、画面表示時に JSONPlaceholder の todos を取得して一覧表示するアプリを完成させよ。___ は全部で5箇所ある。

<script setup>
import { ref, ___ } from 'vue'             <!-- ___1: 画面表示時に1回実行するライフサイクル関数 -->
const items = ref([])

___ function loadItems() {                  <!-- ___2: 非同期関数を宣言するキーワード -->
  const res = ___ fetch('https://jsonplaceholder.typicode.com/todos')  <!-- ___3: 非同期処理の結果を待つキーワード -->
  items.value = ___ res.json()              <!-- ___4: 同じく結果を待つ -->
}

___(loadItems)                              <!-- ___5: ___1 で import した関数を呼ぶ -->
</script>

<template>
  <p>件数: {{ items.length }}</p>
  <ul>
    <li v-for="item in items" :key="item.id">{{ item.title }}</li>
  </ul>
</template>

確認方法

  1. App.vue に貼り付けて npm run dev で起動
  2. ページを開くと自動で 200 件のリストが表示される
  3. F12 → Network タブで todos リクエストが見える

ドリル 1: ヒント

ドリル名: 01-fill-fetch

> ⚠️ 自力で考えてから読んでください

  • ___1 / ___5: 画面表示時に 1 回実行する Vue のライフサイクル関数(on から始まる)
  • ___2: 非同期関数の宣言キーワード(4 文字)
  • ___3 / ___4: Promise の解決を待つキーワード(5 文字)

ドリル 2: fetch + クエリパラメータ + v-if/v-else - 検索アプリを白紙から

ドリル名: 02-build-search-app

> 🛠️ 【タイプ: ゼロベース実装問題】 ほぼ空の App.vue から仕様を読んで自力で実装してください。<template> 構造・id/class・内部ロジックは完全に自由。ただしテストが見る 画面の表示テキスト・ボタンの動作 は仕様どおりに揃えること。

問題

Vue で「キーワード検索 → 結果表示」のミニアプリを App.vue白紙から 実装してください。

仕様

API はモック (テストで fetch を stub):

GET http://localhost:8000/search?q=<keyword>
→ [{ "id": 1, "title": "..." }, ...]

画面要件

  • 検索キーワード入力欄 (テキスト)
  • 「検索」ボタン クリックで fetch → 結果一覧を表示
  • 結果が空なら 「該当なし」 と表示
  • ローディング中は 「検索中...」 と表示 (任意、テストはここを必須にしない)

守る (テストが見ている)

  • 「検索」ボタンが画面にある
  • 「検索」クリックで fetch が呼ばれる (URL に /search を含む)
  • 入力したキーワードが URL の q= パラメータに反映される
  • 取得した結果のタイトルが画面に表示される
  • 結果が空のとき「該当なし」と表示

このドリルで学ぶこと

学習要素学習資料との対応
テンプレートリテラルでクエリパラメータ付き URL を組み立てるch04 slide「URL にクエリパラメータを埋める」
encodeURIComponent で値を安全にエスケープch04 slide「URL にクエリパラメータを埋める」
ボタンクリックで fetch を発火 (onMounted との対比)ch04 slide「ボタンクリックで fetch を発火」
v-if / v-else で結果の有無を分岐表示ch04 slide「結果の有無で表示を分ける」

このドリルは 「非同期処理 + 分岐UI」の最小形。実務でほぼ必ず出る検索画面パターンを体得します。

ドリル 2: ヒント

ドリル名: 02-build-search-app

> ⚠️ 自力で考えてから読んでください

段階1: 入力欄と結果配列

import { ref } from 'vue'
const keyword = ref('')
const results = ref([])

段階2: 検索ボタンと fetch

async function search() {
  const url = `http://localhost:8000/search?q=${keyword.value}`
  const res = await fetch(url)
  results.value = await res.json()
}

段階3: 結果表示と「該当なし」

<ul v-if="results.length > 0">
  <li v-for="item in results" :key="item.id">{{ item.title }}</li>
</ul>
<p v-else>該当なし</p>

ただし「最初から該当なしと出る」のは違和感あるので searched フラグで「一度でも検索したか」を持つと自然。

ch04-fetch 演習解説

⚠️ 自力でドリルを解いてから読んでください

ドリル 1: fetch + onMounted + async/await - API データ取得 — 模範解答

ドリル名: 01-fill-fetch

<script setup>
import { ref, onMounted } from 'vue'
const items = ref([])

async function loadItems() {
  const res = await fetch('https://jsonplaceholder.typicode.com/todos')
  items.value = await res.json()
}

onMounted(loadItems)
</script>

<template>
  <p>件数: {{ items.length }}</p>
  <ul>
    <li v-for="item in items" :key="item.id">{{ item.title }}</li>
  </ul>
</template>

ドリル 1: fetch + onMounted + async/await - API データ取得 — 解説

ドリル名: 01-fill-fetch

空欄解答
___1onMounted
___2async
___3await
___4await
___5onMounted

観察ポイント

  • async を付けずに await を書く受講生が多い → エラーメッセージで気づける
  • onMounted(() => loadItems()) のように関数を渡してもよい(同じ結果)
  • onMounted(loadItems())() を付けるとその場で実行されて壊れる → 関数を「渡す」概念の理解

ドリル 2: fetch + クエリパラメータ + v-if/v-else - 検索アプリを白紙から — 模範解答

ドリル名: 02-build-search-app

<script setup>
import { ref } from 'vue'

const keyword = ref('')
const results = ref([])
const searched = ref(false)

async function search() {
  const url = `http://localhost:8000/search?q=${encodeURIComponent(keyword.value)}`
  const res = await fetch(url)
  results.value = await res.json()
  searched.value = true
}
</script>

<template>
  <div>
    <input v-model="keyword" type="text" placeholder="キーワード" />
    <button @click="search">検索</button>
    <ul v-if="results.length > 0">
      <li v-for="item in results" :key="item.id">{{ item.title }}</li>
    </ul>
    <p v-else-if="searched">該当なし</p>
  </div>
</template>

ドリル 2: fetch + クエリパラメータ + v-if/v-else - 検索アプリを白紙から — 解説

ドリル名: 02-build-search-app

検索 UI のパターン

  1. 入力 → クリック → fetch → 表示 の流れ
  2. URLエンコード : encodeURIComponent(keyword.value) で日本語や記号を安全に
  3. 空結果のハンドリング : v-if="results.length > 0" で分岐、v-else で「該当なし」
  4. 初回表示 : 検索前は何も表示しないのが自然 → searched フラグ

実装が複数あり得る点

  • 検索のトリガー: ボタンクリック / Enter キー押下 / @input でインクリメンタル検索
  • ローディング表示: あり / なし
  • 検索結果の型: string[] / {id, title}[] / {id, title, description}[]
  • エラー処理: try/catch / fetch の res.ok チェック

Q: fetch + onMounted の空欄を埋める

問題

___ を埋めて、画面表示時に JSONPlaceholder の todos を取得して一覧表示するアプリを完成させよ。___ は全部で5箇所ある。

<script setup>
import { ref, ___ } from 'vue'             <!-- ___1: 画面表示時に1回実行するライフサイクル関数 -->
const items = ref([])

___ function loadItems() {                  <!-- ___2: 非同期関数を宣言するキーワード -->
  const res = ___ fetch('https://jsonplaceholder.typicode.com/todos')  <!-- ___3: 非同期処理の結果を待つキーワード -->
  items.value = ___ res.json()              <!-- ___4: 同じく結果を待つ -->
}

___(loadItems)                              <!-- ___5: ___1 で import した関数を呼ぶ -->
</script>

<template>
  <p>件数: {{ items.length }}</p>
  <ul>
    <li v-for="item in items" :key="item.id">{{ item.title }}</li>
  </ul>
</template>

確認方法

  1. App.vue に貼り付けて npm run dev で起動
  2. ページを開くと自動で 200 件のリストが表示される
  3. F12 → Network タブで todos リクエストが見える

ヒント

  • ___1 / ___5: 画面表示時に 1 回実行する Vue のライフサイクル関数(on から始まる)
  • ___2: 非同期関数の宣言キーワード(4 文字)
  • ___3 / ___4: Promise の解決を待つキーワード(5 文字)

Q: 検索アプリを ゼロから実装する

問題

Vue で「キーワード検索 → 結果表示」のミニアプリを App.vue白紙から 実装してください。

仕様

API はモック (テストで fetch を stub):

GET http://localhost:8000/search?q=<keyword>
→ [{ "id": 1, "title": "..." }, ...]

画面要件

  • 検索キーワード入力欄 (テキスト)
  • 「検索」ボタン クリックで fetch → 結果一覧を表示
  • 結果が空なら 「該当なし」 と表示
  • ローディング中は 「検索中...」 と表示 (任意、テストはここを必須にしない)

守る (テストが見ている)

  • 「検索」ボタンが画面にある
  • 「検索」クリックで fetch が呼ばれる (URL に /search を含む)
  • 入力したキーワードが URL の q= パラメータに反映される
  • 取得した結果のタイトルが画面に表示される
  • 結果が空のとき「該当なし」と表示

このドリルで学ぶこと

学習要素学習資料との対応
テンプレートリテラルでクエリパラメータ付き URL を組み立てるch04 slide「URL にクエリパラメータを埋める」
encodeURIComponent で値を安全にエスケープch04 slide「URL にクエリパラメータを埋める」
ボタンクリックで fetch を発火 (onMounted との対比)ch04 slide「ボタンクリックで fetch を発火」
v-if / v-else で結果の有無を分岐表示ch04 slide「結果の有無で表示を分ける」

このドリルは 「非同期処理 + 分岐UI」の最小形。実務でほぼ必ず出る検索画面パターンを体得します。

ヒント

段階1: 入力欄と結果配列

import { ref } from 'vue'
const keyword = ref('')
const results = ref([])

段階2: 検索ボタンと fetch

async function search() {
  const url = `http://localhost:8000/search?q=${keyword.value}`
  const res = await fetch(url)
  results.value = await res.json()
}

段階3: 結果表示と「該当なし」

<ul v-if="results.length > 0">
  <li v-for="item in results" :key="item.id">{{ item.title }}</li>
</ul>
<p v-else>該当なし</p>

ただし「最初から該当なしと出る」のは違和感あるので searched フラグで「一度でも検索したか」を持つと自然。

解説

検索 UI のパターン

  1. 入力 → クリック → fetch → 表示 の流れ
  2. URLエンコード : encodeURIComponent(keyword.value) で日本語や記号を安全に
  3. 空結果のハンドリング : v-if="results.length > 0" で分岐、v-else で「該当なし」
  4. 初回表示 : 検索前は何も表示しないのが自然 → searched フラグ

実装が複数あり得る点

  • 検索のトリガー: ボタンクリック / Enter キー押下 / @input でインクリメンタル検索
  • ローディング表示: あり / なし
  • 検索結果の型: string[] / {id, title}[] / {id, title, description}[]
  • エラー処理: try/catch / fetch の res.ok チェック