規則:可以查前面的課、MDN、API 文件,也可以使用 coding agent;但不要直接複製一個完整成品。這一關驗收的是你能不能自己拆問題、找出資料流、除錯並完成作品。

你的任務:做一個 Todo Explorer

建立一個新的前端專案,從公開 API 載入 todo 資料,讓使用者可以查看、篩選、搜尋與重新載入。

資料來源可以沿用上一課的:

https://jsonplaceholder.typicode.com/todos

這不是要你做完整待辦 App,因為目前還沒有 backend 與 database。你的任務是把「外部資料 → 狀態 → 使用者操作 → 畫面更新」做完整。

API → fetch → JavaScript state → filter/search → render → DOM

先建立乾淨的新專案

不要直接在上一課練習頁上繼續堆。建立:

todo-explorer/
├─ index.html
├─ styles.css
├─ script.js
├─ README.md
└─ .gitignore

用本機 HTTP server 執行,例如:

python -m http.server 8000

或 Windows:

py -m http.server 8000

再從 http://localhost:8000 開啟。

Checkpoint A|先把 HTML 結構做好

在還沒有任何 JavaScript 前,頁面就應該有合理的語意結構。

必須有:一個主標題與簡短說明。

必須有:一個搜尋輸入框。

必須有:篩選控制:All / Active / Completed。

必須有:Reload 按鈕。

必須有:顯示 loading / error / summary 的 status 區域。

必須有:放 todo items 的列表或卡片容器。

不要因為等等 JavaScript 會動態產生內容,就把 HTML 全部縮成一個空 div。固定的頁面骨架仍然應該由 HTML 表達。

Checkpoint B|先讓 CSS 在沒有資料時也合理

頁面至少要做到:

內容寬度不會在大螢幕無限拉長。

搜尋與篩選控制在窄螢幕不會爆版。

todo item 有清楚的 completed / active 視覺差異。

按鈕與輸入框有基本 focus 狀態。

至少使用一次 Flexbox 或 Grid 做真正的 layout。

手機寬度下不出現明顯水平捲動。

這一關不是設計比賽,但不能回到「瀏覽器預設樣式全部沒整理」的狀態。

Checkpoint C|定義你的 JavaScript state

不要把資料散落在 DOM 裡。先想清楚程式需要記住什麼。

一個合理的最小 state 可能包含:

let todos = [];
let currentFilter = "all";
let searchQuery = "";
let isLoading = false;
let errorMessage = "";

你不必一模一樣照抄,但要能回答:哪個變數代表 API 原始資料?哪個代表使用者現在選的 filter?哪個代表搜尋文字?

State 是資料真相 → DOM 是目前狀態的呈現

Checkpoint D|載入資料

建立一個專門負責 request 的 async function。它至少要處理:

開始 request 前進入 loading 狀態。

使用 fetch() 發出 GET request。

檢查 response.ok。

使用 response.json() 解析資料。

成功後把資料存進 state。

失敗時顯示使用者看得懂的錯誤訊息。

finally 或等價流程中解除 loading 狀態。

建議一開始只取一小部分資料,例如:

todos.slice(0, 30)

不是因為瀏覽器不能顯示更多,而是這一關的重點是資料流,不是把 200 筆假資料全部堆到畫面上。

Checkpoint E|把 render 和 fetch 分開

不要讓一個 loadTodos() 同時負責抓資料、篩選、建立 DOM、處理搜尋、更新所有按鈕。

至少嘗試拆成類似:

loadTodos()
getVisibleTodos()
renderTodos()
renderStatus()

名稱可以不同,但職責要分開。

load → 更新 state
filter/search → 從 state 算出要顯示的資料
render → 把結果畫到 DOM

Checkpoint F|完成 Filter

使用者要可以選:

All → 全部
Active → completed === false
Completed → completed === true

篩選時不要重新 fetch。API 資料已經在 state 裡,這時只是改變「哪些資料要被顯示」。

也就是:

filter event → currentFilter 改變 → 重新計算 visible todos → render

Checkpoint G|完成搜尋

搜尋框至少要依 todo 的 title 篩選。

建議把大小寫問題先處理掉:

todo.title.toLowerCase().includes(query.toLowerCase())

如果搜尋是即時更新,可以監聽 input;如果你想做成按下搜尋才執行,也可以使用 form + submit。

重點不是事件種類,而是你能解釋:

user input → state 改變 → visible data 改變 → DOM 重新 render

Checkpoint H|Filter 和 Search 要能一起工作

這是整關最重要的邏輯之一。

不能出現:

搜尋之後 filter 失效。

切 filter 之後搜尋文字被忘記。

每個 event handler 都自己寫一套完全不同的篩選邏輯。

比較好的思路是:每次畫面要更新時,都從完整 state 計算一次「目前真正應該顯示哪些 todos」。

Checkpoint I|Reload 要真的重新發 request

Reload 按鈕不是 location.reload()。

它應該再次執行你的資料載入函式:

Reload click → loading → fetch → state 更新 → render

Loading 期間可以暫時 disable 按鈕,避免連點造成多個 request。

Checkpoint J|Error state 不能只藏在 Console

開發者需要 Console,但使用者看不到你的 Console。

你要故意把 API URL 改錯一次,確認:

頁面真的會顯示 Error 狀態。

Reload 不會永遠卡在 disabled。

舊資料是否保留或清空,是你有意識做的決定。

Console 仍然保留足夠資訊讓開發者除錯。

測完再把 URL 改回正確版本。

Checkpoint K|DOM 建立要保持安全與可讀

API 的 title 是文字,所以顯示時使用:

element.textContent = todo.title;

不要為了方便把 API 字串直接塞進大型 innerHTML template。

這一關至少要使用 createElement、classList、append 或同等 DOM API 建立主要資料內容。

Checkpoint L|Summary 也要來自資料

頁面上顯示一段摘要,例如:

Showing 8 of 30 · 12 completed

數字不能寫死。要由目前 state 與 visible data 計算出來。

這會逼你再次分清楚:

全部資料數量 ≠ 目前畫面顯示數量 ≠ completed 數量

至少使用一次 DevTools Network 來除錯

在 Network 面板重新載入資料,確認你能找到這次 API request。

至少看過:

Request URL。

Status code。

Response / Preview。

Request 發生的時間點。

如果畫面沒有資料,先判斷是「request 根本沒成功」還是「資料到了,但 render 壞了」。

可以用 AI coding agent,但要留下理解

Codex、Claude Code、AGY 都可以幫你做這個專案,但至少遵守三件事:

在接受變更前看 diff。

它新增函式時,你要能說出函式的 input / output / responsibility。

它改動 fetch、state 或 render 流程時,你要重新測 loading / success / error 三種狀態。

如果 agent 一次產生 300 行而你完全不知道資料怎麼流,這一關就還沒有完成。

Checkpoint M|用 Git 留下可讀歷史

這不是 Start Here 的 Git 重考,但既然現在是真正的小專案,就不要回到「最後全部一次 commit」。

至少留下三個有意義的 commits,例如分成:

建立 HTML / CSS 基礎
加入 API 載入與 render
加入搜尋、filter 與錯誤狀態

訊息自己寫。完成後推到 GitHub。

加分,但不是完成條件

如果核心功能都穩定,可以再選一兩個:

顯示目前 active filter 的視覺狀態。

加入 Empty state,例如「沒有符合搜尋條件的資料」。

加入簡單排序。

把 URL / selectors / render helpers 整理得更乾淨。

顯示最後成功載入時間。

不要在核心功能還壞掉時先做動畫、漸層或十個花俏功能。

最終驗收

□ HTML 有語意結構,不是只有一個空容器。

□ CSS 在桌機與手機寬度都能正常閱讀。

□ JavaScript 有明確 state,不依賴 DOM 當唯一資料來源。

□ 我能成功 fetch API,並檢查 response.ok。

□ Loading / Success / Error 三種狀態都測過。

□ API array 能被 render 成多個 DOM elements。

□ Search 與 All / Active / Completed filter 可以一起使用。

□ Reload 真的會重新發出 request。

□ 我用過 DevTools Console、Elements、Network 除錯。

□ 使用者看得到錯誤,而不是只有 Console 有訊息。

□ 我沒有把任何 secret / API key 放在前端。

□ Git history 至少有三個有意義的 commits,而且已推到 GitHub。

如果全部做到,你現在會的是什麼?

你還沒有學 framework,也還沒有 backend。但你已經具備完整的前端基本資料流:

HTML 結構
↓
CSS presentation
↓
JavaScript state / logic
↓
Events 接收使用者操作
↓
Fetch 取得外部資料
↓
DOM render 畫面

這比「會抄 React component」更重要,因為接下來任何 framework 都只是在這些概念上提供新的組織方式。

下一條路徑是 Web App:前端不再只讀別人的 API,而是開始建立自己的 backend、database、authentication 與部署流程。

Web Basics 完成條件:你能在不依賴前端 framework 的情況下,從零做出一個 responsive、可互動、會呼叫 API、會處理 loading / error、並能把資料安全渲染到 DOM 的小網站。