
【JavaScript入門】過去投稿のタイムライン表示と「いいね」(postListArea.js)
postListArea.js
は サーバーから取得した投稿一覧を描画 し、ユーザーが ❤️ 「いいね!」ボタンを押すたびに リアルタイムでカウントを更新 するモジュールです。
ページ読み込み直後と投稿完了後に updatePostList()
を呼び出すことで、画面遷移なしで最新タイムラインを再表示できます。いいね数は「楽観的 UI 更新 → サーバー確定値で上書き」という 2 段階方式を採用し、スナッピーな操作感と整合性を両立しています。

フォルダ構成(該当部分)
node-js/
└─ app/
├─ index.html
├─ style.css
└─ js/
├─ common.js
├─ storage.js
├─ imageArea.js
├─ imageEffects.js
├─ controlArea.js
├─ commentArea.js
├─ sendArea.js
├─ dateFormat.js
└─ postListArea.js ← ★今回実装
postListArea.js ― フルコード & 詳細コメント
/* ---------------------------------------------------
postListArea.js (フロントのみで完結)
- 過去投稿タイムラインを Ajax で取得して描画
- ❤️ いいね!をクリックすると即座に +1
- サーバー戻り値が文字列でも安全に数値化
- 連打防止に disabled 制御
--------------------------------------------------- */
window.addEventListener('DOMContentLoaded', () => {
updatePostList(); // 初回ロード
document.querySelector('#postListArea')
.addEventListener('click', onLikeClicked); // いいね!はイベント委任
});
/* ========== タイムライン取得 ========== */
function updatePostList(){
const root = document.querySelector('#postListArea');
fetch('/get') // ← API 例: /get
.then(r => {
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return r.json(); // { list:[…] } を想定
})
.then(({ list }) => {
root.innerHTML = ''; // 旧 DOM を一掃
list.forEach(post => root.appendChild(buildItem(post)));
})
.catch(console.error);
}
/* ========== 投稿 1 件を DOM 化 ========== */
function buildItem(post){
const wrap = document.createElement('div');
wrap.className = 'postListItem';
const likeCnt = parseInt(post.likes, 10) || 0; // 文字列対策 (例 "5")
wrap.innerHTML = `
<div class="plImage">
<img src="/${post.dir}${post.image}" alt="投稿画像">
</div>
<div class="plBody">
<p class="plComment">${post.comment}</p>
<div style="display:flex;justify-content:space-between;align-items:center;">
<time class="plDate">
${dateFormat('YYYY/MM/DD hh:mm:ss', new Date(post.date))}
</time>
<button type="button"
class="likeBtn"
data-id="${post.id}">
❤️ <span>${likeCnt}</span>
</button>
</div>
</div>`;
return wrap;
}
/* ========== いいね!処理 (イベント委任) ========== */
function onLikeClicked(e){
const btn = e.target.closest('button.likeBtn'); // img や span クリック対策
if (!btn) return;
e.preventDefault();
const id = btn.dataset.id; // 投稿 ID
const span = btn.querySelector('span');
let cnt = parseInt(span.textContent, 10) || 0;
/* ➀ 楽観的 UI 更新 (即時 +1) */
btn.disabled = true; // 連打防止
span.textContent = ++cnt; // 1 行でカウントアップ
/* ➁ サーバーへ POST */
fetch(`/like/${encodeURIComponent(id)}`, {
method: 'POST',
credentials: 'same-origin',
headers: { 'Accept': 'application/json' }
})
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const ct = res.headers.get('Content-Type') || '';
return ct.includes('application/json') ? res.json() : null;
})
.then(data => {
// サーバーが正式な likes を返せば UI を上書き
if (data && data.likes != null) {
const serverCnt = parseInt(data.likes, 10);
if (!Number.isNaN(serverCnt)) span.textContent = serverCnt;
}
})
.catch(err => {
console.error('Like リクエストに失敗:', err);
// エラー時は楽観的更新を維持 (必要ならリトライ UI を実装)
})
.finally(() => { btn.disabled = false; });
}
主要命令まとめ
命令 / API | 働き | 主な使い方 |
---|---|---|
fetch('/get') | 投稿リストを取得 (GET) | JSON で { list:[…] } を返す API を想定 |
Element.innerHTML = '' | 子要素を一掃 | タイムラインを丸ごとリフレッシュ |
`parseInt(str,10) | 0` | |
.closest('button.likeBtn') | イベント委任先のボタン探索 | 画像クリックなどでも正しく拾う |
btn.disabled = true | クリック一時無効化 | 二重送信・連打対策 |
encodeURIComponent(id) | URL 埋め込み時のエスケープ | /like/{id} を安全生成 |
credentials:'same-origin' | Cookie 同送 | セッション認証用 |
Content-Type 判定 | JSON 応答だけを res.json() | HTML 返しなどでも落ちない |
関数別ロジック解説
関数 | 処理フロー |
---|---|
updatePostList() | 1. /get から JSON 取得 → 2. 既存 DOM 削除 → 3. buildItem() で投稿を順に生成し挿入 |
buildItem(post) | 投稿 1 件を HTML 文字列で組み立て → 包装 div を返す。data-id に投稿 ID を埋めて “いいね!” の識別に利用 |
onLikeClicked(e) | ① 楽観的 UI 更新(+1 & disabled)→ ② /like/{id} へ POST → ③ サーバー戻り値があれば確定値で上書き → ④ 失敗時はログのみ残して UI 維持 |
実装ポイント & ベストプラクティス
- 楽観的 UI 更新
ネットワーク待ちを感じさせないため、まずクライアント側で +1 表示し、後でサーバー確定値で調整します。 - イベント委任
タイムラインは再描画で DOM が入れ替わるため、親要素 (#postListArea
) にクリックハンドラを 1 本だけ付与し、.closest()
でボタンを特定します。 - 文字列数値化の徹底
JSON フィールドが"7"
のような文字列で来ても安全にparseInt
→|| 0
で扱い、NaN
を防ぎます。 - 障害時の UX
送信失敗でも「見た目は +1 のまま」ですが、次にupdatePostList()
が走るとサーバー側の正しい値に戻るため整合性は保てます。必要に応じてリトライ UI を追加してください。
これで 「みんなのつぶやき」アプリの主要フロントエンド実装 が一巡しました。
次回は ローカル + サーバーでの動作確認手順 をまとめ、デバッグのコツやよくあるエラー対処を解説します。