📖 テーマ設定
🔊 音声設定
1.2
1.0
1.0
▶️ 再生コントロール
🎵 BGM設定
0.3
🔔 効果音設定
0.3

開発テーマに沿った実装②(UI/UX結合)

概要

  • 日程: Day 4 / セッション 04
  • 時間: 11:35-12:00
  • 形式: 実習
  • ゴール: Day 2のモックアップを元に、UIからAPIをfetchで呼び出すコードを実装し、画面上に取得結果が表示される
  • 学習形式: ハンズオン実習(AIペアプログラミング)

導入(5分)

ここまでで「画面の絵(モックアップ)」と「APIの本体(CRUD)」が別々に揃いました。残りは両者をJSONというメッセージでつなぐだけ。25分の短いセッションですが、最も「動いた!」を感じやすい時間です。

ここで思い出してほしいことがあります。Day 3 Session08で学んだRESTでは、「サーバは状態を持たず、リソースをJSONで返す」のがルールでした。UI側のJavaScriptは、そのJSONを受け取って画面に並べるだけ。逆に画面でユーザが入力したものを、JSONにしてサーバに渡すだけ。役割が明確に分かれているのを実感する25分にしましょう。

本編(10分)

1. fetchによるAPI呼び出しの基本

JavaScriptのfetchは、ブラウザ標準のHTTPクライアントです。Promiseベースで4つの操作すべて書けます。

const BASE = 'http://localhost:3000';

// GET(一覧)
async function listRecipes() {
  const res = await fetch(`${BASE}/recipes`);
  return await res.json();
}

// GET(詳細)
async function getRecipe(id) {
  const res = await fetch(`${BASE}/recipes/${id}`);
  if (!res.ok) throw new Error('not found');
  return await res.json();
}

// POST(新規)
async function createRecipe(data) {
  const res = await fetch(`${BASE}/recipes`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data)
  });
  return await res.json();
}

// PUT(更新)
async function updateRecipe(id, data) {
  const res = await fetch(`${BASE}/recipes/${id}`, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data)
  });
  return await res.json();
}

// DELETE
async function deleteRecipe(id) {
  await fetch(`${BASE}/recipes/${id}`, { method: 'DELETE' });
}

ここがポイント

  • fetchHTTPステータスが400/500でもrejectしないres.okで自分で判定する
  • リクエストボディは必ずJSON.stringify、ヘッダにContent-Type: application/json
  • レスポンスはawait res.json()でオブジェクトに戻す

コラム:XMLHttpRequest からの解放

2010年代まで、JavaScriptでAPIを叩くにはXMLHttpRequest(XHR)という名前長め+コールバック地獄なAPIを使うのが普通でした。onreadystatechangeを書いてはネストし、エラー処理は手書き。jQueryの$.ajaxがデファクトになったほどです。2015年にfetchが標準化され、async/awaitと組み合わせて「ほぼ同期コードのように」書けるようになりました。皆さんはストレスのない時代に学べています。

2. UIにつなぐ最小サンプル

HTMLとJavaScriptだけの最小例です。Monaca・VSCode・GitHub Pagesどこでも動きます。

<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>レシピ一覧</title></head>
<body>
  <h1>レシピ一覧</h1>
  <ul id="list"></ul>

  <h2>新規追加</h2>
  <input id="title" placeholder="タイトル">
  <input id="time" type="number" placeholder="所要分">
  <button id="add">追加</button>

  <script>
    const BASE = 'http://localhost:3000';
    const $list = document.getElementById('list');

    async function render() {
      const items = await fetch(`${BASE}/recipes`).then(r => r.json());
      $list.innerHTML = items
        .map(i => `<li>${i.title}(${i.time}分)<button data-id="${i.id}">削除</button></li>`)
        .join('');
    }

    document.getElementById('add').onclick = async () => {
      const title = document.getElementById('title').value;
      const time = Number(document.getElementById('time').value);
      await fetch(`${BASE}/recipes`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ title, time })
      });
      render();
    };

    $list.onclick = async (e) => {
      if (e.target.tagName === 'BUTTON') {
        await fetch(`${BASE}/recipes/${e.target.dataset.id}`, { method: 'DELETE' });
        render();
      }
    };

    render();
  </script>
</body>
</html>

ここがポイント

  • HTMLは情報の入れ物、JavaScriptがAPIとの仲介役
  • 操作のたびにrender()して画面を作り直すパターンが最も簡単
  • 本格化したらReact/Vueに切り替えてOK。基本構造は同じ

3. プレゼンテーションと機能の分離を実感する

sequenceDiagram participant U as ユーザ participant V as UI participant A as API participant D as データ U->>V: 「追加」ボタン押下 V->>A: POST /recipes JSON A->>D: 配列にpush A-->>V: 201 Created JSON V->>A: GET /recipes A-->>V: 200 JSON配列 V-->>U: 一覧を再描画

UIは「ユーザに見せて受け取る」だけ、APIは「データを保持して整える」だけ。両者の知識が混ざらない。これがDay 4最大のテーマでした。

ここがポイント

  • UIのコードに「データの保存ロジック」が混ざっていたら設計違反
  • APIのコードに「画面の表示順」が混ざっていたら設計違反
  • 自分のコードはどっちの仕事?」を常に自問する

コラム:SPAという潮流

2010年代前半、Gmail / Trello / Slack のような「画面遷移なしでサクサク動くWebアプリ」(SPA: Single Page Application)が増えました。これはUIとAPIを完全分離した結果生まれたスタイルです。今日の皆さんの構成はミニSPAそのもの。React / Vue / Angular といったフレームワークも、本質は同じ「UI担当」です。

💬 AIに聞いてみよう

ここまでの内容で疑問があれば、AIに質問してみましょう。たとえば:

  • 「自分のチームのモックアップに、fetchで一覧表示を組み込んで」
  • 「CORSエラーが出た。原因と直し方は?」
  • 「fetchの結果を画面表示する処理が長くなる。きれいに書く方法は?」

実習(10分)

課題

Day 2で作ったモックアップから1画面を選び、対応するAPIエンドポイントをfetchで呼び出して、画面に取得結果を表示してください。可能なら「新規追加 → 一覧更新」までやってみましょう。

成果物

  • HTML+JS(または React/Vue 等)のソースコード(GitHub)
  • 画面のスクリーンショット または 簡易動画
  • 動作確認した手順メモ

ヒント

  • いきなり全画面ではなく、まず1画面 = 1ユースケースに集中
  • CORSで詰まったら、API側に corsミドルウェアを入れる
  • APIが未完成な部分は、Session02のapiaryモックURLを叩く(仕様が同じなら差し替えるだけ)
  • 詰まったらAIに「次のHTMLとAPIの仕様で、fetchを使った一覧表示コードを書いて」と頼む

まとめ(5分)

UIからfetchでAPIを叩き、JSONを受け取って画面に描く。たったこれだけの仕組みで、現代のWebアプリのほとんどは動いています。「プレゼンテーションと機能の分離」が、絵に描いた餅でなく実際に手元で動いた瞬間です。

午後の最初のセッションでは、ここまで作ったAPIとUIをつなぎ込み、エンドツーエンドで1つのユースケースを動かす仕上げを行います。

🔄 振り返りチェック

  • fetchでGET/POST/PUT/DELETEをそれぞれ書けますか?
  • なぜ「UIにロジックを混ぜない」が大事なのか説明できますか?
  • CORSとは何で、なぜ発生するか言えますか?

補足資料

  • 参考リンク: MDN fetch API、Monaca公式チュートリアル
  • 発展課題: ローディング表示・エラーメッセージの表示・楽観的UI更新を実装する

学習ガイド

想定される質問と回答例

質問 ヒント
fetchで400/500の時もエラーにならない res.okを見て自分でthrowする
ボタン連打で同じデータが複数追加される 連打防止フラグ、または送信ボタンを一時無効化する
入力欄に古い値が残る 送信成功後、input.value = ''でクリア

つまずきやすいポイント

つまずきポイント ヒント
Content-Typeを付け忘れる サーバがreq.bodyを読めず空になる
配列が無いのにmapしてエラー Array.isArray()で安全チェック、または初期値[]
ファイル://で開いてfetchが失敗 ローカルサーバ(npx serve等)越しに開く
読み上げを開始します...

AIに質問する