Day 18: 期中實戰 —— 純 JS 打造你的第一個「任務佈告欄」
歷經了變數範圍、函式重構、陣列遍歷、DOM 操控、事件監聽與本地存檔的修煉,今天我們迎來了前半段旅程的盛大集結!本章節將不引入額外的新語法,而是將過去 17 天所學融會貫通,帶領冒險者使用純原生 JavaScript 親手打造一座具備完整 CRUD 功能的「公會任務佈告欄(QuestBoard)」。我們將深入剖析「資料驅動(Data-Driven)」的架構哲學,掌握「以資料狀態為核心,透過單向渲染函式同步更新畫面」的現代前端思維,並完美整合 LocalStorage 實現狀態持久化。
本文同步分享於2026鐵人賽:JS 核心重構:勇者轉職傳說
孩子,在踏上今日的領地前,先坐下來喝杯酒吧。 修煉的路很長,別急著奔跑,先聽聽這份卷軸裡的古老叮嚀。
我在每一站的盡頭都為你鋪設了 「
實戰演武場 CodePen」。那不只是程式碼的拼湊,而是你在這場暴風雨中,試圖用邏輯點燃的一顆星火。 成功突圍後,別忘了帶著你的戰利品回到 「QuestBoard 佈告欄」。在那裡,你會發現自己從不孤單,公會的夥伴們都正舉杯等待你的歸來。記住...
「 導師看重的從來不是你的劍法有多華麗,而是當你被邏輯擊潰、滿身泥濘後,依然選擇握緊理性的劍柄,再次向真相發起衝鋒的勇氣。 」
🛡️ 【公會大廳:導師的修煉導讀】
「今天,我們不教新的招式。我們要來一場盛大的集結。」
我指著公會中央的任務版,那裡是所有冒險者的起點。我們要在那裡練習軟體開發中最重要的四個動作:C (新增)、R (讀取)、U (更新)、D (刪除)。這一章,我們將拆解每一個動作的靈魂,教你如何用純 JS 的邏輯,讓資料與畫面同步舞動。
💡 【技術核心:CRUD 的深度引導】
1. [Read] 資料與畫面的「同步之舞」
在開始任何互動動作前,你必須先學會怎麼「看」。
畫面的根源不是 HTML 結構,而是身為核心真相的 quests 陣列!
我們要建立一個標準的 renderQuests() 渲染函式。它的運作就像影分身:先清空當前牆面,再根據最新資料名單逐張重新貼上。
function renderQuests() {
// 1. 先清空舊牆面,防止重複累加
questList.innerHTML = "";
// 2. 根據陣列名單,一張一張重新渲染生成標籤
quests.forEach((item, index) => {
questList.innerHTML += `
<li class="${item.isDone ? 'completed' : ''}">
<span>${item.title}</span>
<button onclick="toggleQuest(${index})">切換</button>
<button onclick="deleteQuest(${index})">刪除</button>
</li>
`;
});
// 3. 畫面同步完成後,順便執行帳本存檔術 (LocalStorage)
localStorage.setItem("quests", JSON.stringify(quests));
}
導師提點: 只要你的
render邏輯寫得精準,接下來所有的操作都只需要專注於「修改陣列資料 ➡️ 呼叫renderQuests()」。
2. [Create] 捕捉冒險者的計畫
當小冒險者在輸入框打字並按下「發布任務」按鈕時,就是 C (Create) 的時刻。
⚡ 核心心法: 永遠不要直接把 <input> 的文字直接插進 DOM 樹,而是要將其抽象為一個具備狀態的 「物件」,並 push 推進核心陣列中。
addBtn.addEventListener("click", () => {
// 1. 抓取卷軸內容 (Input Value)
const title = questInput.value.trim();
if (!title) return alert("請輸入任務名稱!");
// 2. 將輸入轉化為標準任務物件,加入冒險陣列清單
const newQuest = { title: title, isDone: false };
quests.push(newQuest);
// 3. 清空輸入框並重新觸發渲染 (Data -> UI)
questInput.value = "";
renderQuests();
});
3. [Update] 任務狀態的完成與切換
當任務標記為「已完成」,這涉及到修改資料陣列中特定物件的狀態。
⚡ 核心心法: 透過索引值(Index 或 ID)找到陣列中的那筆資料,將布林值 isDone 反轉(Toggle),再交由渲染函式刷新外觀。
window.toggleQuest = function (index) {
// 1. 找到對應索引的任務資料,切換布林開關 (True/False 取反)
quests[index].isDone = !quests[index].isDone;
// 2. 重新渲染畫面,樣式會自動依照 isDone 動態上色
renderQuests();
};
導師提點: 畫面上的刪除線與灰階樣式,是在
renderQuests時根據isDone資料動態決定的,這正是「以資料決定畫面」的典型示範。
4. [Delete] 放棄過期任務的決斷
有時候,冒險者必須捨去過期或無法執行的任務。
⚡ 核心心法: 從陣列中徹底剔除該項目(splice),並重新觸發單向渲染。
window.deleteQuest = function (index) {
// 1. 從陣列中精準剔除該筆資料
quests.splice(index, 1);
// 2. 重繪佈告欄並同步更新 LocalStorage
renderQuests();
};
💡 【圖解:資料與畫面的單向導引】
CRUD 運作就像是一條單向流動的護城河:所有使用者的互動(新增、點擊、刪除)都只負責「修改陣列資料箱」;而渲染函式就像一座大水閘,只要資料箱一變動,水閘便重新放水繪製出全新的 UI 畫面。

🏰 【勇者精英課:邁向職人的進階架構】
為什麼現代前端不建議「直接手動操作 DOM」?
試想一下:如果你的專案未來新增了一百個功能(搜尋過濾、依難度排序、進度統計、分頁切換),如果你每執行一次動作,都要手動寫 document.createElement、appendChild、classList.remove 與 removeChild,程式碼很快就會陷入狀態不同步的泥淖,成為難以維護的「義大利麵條代碼(Spaghetti Code)」。
透過 「資料驅動 UI(Data-driven UI)」 架構:
- 單一真實來源(Single Source of Truth):你只需要確保 資料陣列(Array) 的正確性。
- 單向宣告式渲染:畫面僅是資料的視覺投影,只要資料對了,畫面就保證永遠正確!
這正是 React、Vue 等現代主流前端框架的核心哲學起點。
孩子,新手在實作 CRUD 時最常犯的兩大失誤:
- 「只改了資料,卻忘了呼叫
render()」 —— 資料變更了,畫面卻紋絲不動。- 「只改了畫面,卻忘了同步寫回 LocalStorage」 —— 一重新整理,剛新增的任務全部消失。
這種「狀態脫鉤」會產生致命的資料幽靈。記住:任何 CRUD 動作的最後一步,永遠是呼叫渲染函式,讓畫面與本機存檔同步歸位。
天色微亮,營火雖已燃盡,但你眼中卻閃爍著領悟的光芒。在前往演武場挑戰關卡前,我已經幫你把靈魂碎片精煉成了這份「戰術錦囊」。拿好它,今日的任務不再是負擔,而是你證明自我的舞台。去吧,公會的英雄榜在等著你的戰報。
📝 【夥伴筆記:今日修煉精華】
這份筆記是你的隨身護身符,卡關時看一眼,真相就在裡面。
- 單一真實來源:畫面一切以 JavaScript
quests陣列資料為準。 - CRUD 四核心:
- Create:將輸入封裝為物件後
push入陣列。 - Read:遍歷陣列生成 HTML 模板並注入容器。
- Update:藉由 Index 查找資料並切換
isDone狀態。 - Delete:使用
splice(index, 1)從陣列中移除項目。
- Create:將輸入封裝為物件後
- 渲染前置清空:執行
render前務必先清空container.innerHTML = ""防止重複堆疊。 - 自動狀態持久化:在
render函式結尾統一執行localStorage.setItem,達成全面自動同步。
🎯 【實戰演武場】
⚔️ 任務鑑定條件:
- 完成 📜 本日實戰任務 (CodePen)。
- 將 CodePen 網址貼至 QuestBoard,並回填鑑定報告:
- 初心者:CRUD 完整體驗 (成就感發掘):
-
- 當你執行「刪除」動作時,你是直接刪掉 HTML 內容?還是從 Array 移除?
-
- 冒險者:資料驅動的優勢 (理性挑戰):[⚡ 觀念辨析]
-
- 如果你在
renderQuests裡忘記寫list.innerHTML = "",當你新增任務時會發生什麼恐怖的疊加現象?
- 如果你在
-
- 初心者:CRUD 完整體驗 (成就感發掘):
- 任務完成後,你的名字將永遠標記在公會的英雄榜上!
📚 【圖書館卷軸與前後站連結】
- 前導冒險: Day 00: 勇者村的入村儀式
- 上一站冒險: Day 17:原理篇 —— 時間管理大師:Timeout 與 Interval 的實作應用
- 下一站冒險: Day 19:原理篇 —— 為什麼程式不乖乖排隊?JS 腦袋裡的 Event Loop
- MDN 官方文獻: MDN Array.prototype.splice() 詳解
- MDN 官方文獻: MDN Array.prototype.forEach() 詳解
- MDN 官方文獻: MDN Element.innerHTML 詳解
- MDN 官方文獻: MDN Window.localStorage 使用指南