開発テーマに沿った実装②(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' });
}
ここがポイント
fetchはHTTPステータスが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. プレゼンテーションと機能の分離を実感する
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
fetchAPI、Monaca公式チュートリアル - 発展課題: ローディング表示・エラーメッセージの表示・楽観的UI更新を実装する
学習ガイド
想定される質問と回答例
| 質問 | ヒント |
|---|---|
| fetchで400/500の時もエラーにならない | res.okを見て自分でthrowする |
| ボタン連打で同じデータが複数追加される | 連打防止フラグ、または送信ボタンを一時無効化する |
| 入力欄に古い値が残る | 送信成功後、input.value = ''でクリア |
つまずきやすいポイント
| つまずきポイント | ヒント |
|---|---|
Content-Typeを付け忘れる |
サーバがreq.bodyを読めず空になる |
配列が無いのにmapしてエラー |
Array.isArray()で安全チェック、または初期値[] |
| ファイル://で開いてfetchが失敗 | ローカルサーバ(npx serve等)越しに開く |