一開始在規劃 TaskTimer 時,我的核心目標非常明確——簡潔方便、功能單純。
我希望它是一個能讓使用者隨開即用、沒有任何學習門檻的小幫手。更重要的是,我希望它 就算在離線狀態下使用也不受任何影響;同時,盡可能不收集使用者的個人資料,保護隱私與數據安全。
在查詢一些資料後,我發現 Local-First(本地優先) 這個架構理念,很符合我的需求。
Local-First(本地優先) 的核心思想是:「資料的主權屬於使用者,且應用程式的核心功能必須能在本地端獨立運作。」
1. 極致的順暢度(Zero Latency):所有資料讀寫都在瀏覽器內部完成,不需要等待網路 Request 反應,使用者可以順暢操作。
2. 離線即服務(Offline-Ready):就算完全斷開網路,使用者依然可以新增任務、開始計時、寫下備註與查看歷史紀錄。
3. 資料隱私與掌控權:使用者的數據與備註不會預設上傳到第三方伺服器,資料儲存在使用者自己的裝置上,完全保障隱私。
前端常見的本地儲存機制主要有 LocalStorage 與 IndexedDB,兩者的特性差異如下:
| 特性 | LocalStorage | IndexedDB |
|---|---|---|
| 資料 | 僅支援字串 (Key-Value) | 支援結構化物件 (JSON Object/Array) |
| 容量限制 | 極小(通常約 5MB) | 極大(通常可達 幾百 MB 以上) |
| 執行方式 | 同步 (Synchronous),會阻塞 UI 主執行緒 | 非同步 (Asynchronous),不卡死 UI 渲染 |
| 查詢能力 | 只能靠 Key 取值,複雜查詢需全拉出解析 | 支援 Index (索引) 與範圍查詢 |
避免 Blocking UI:LocalStorage 是同步 API,當資料量變大(例如累積了幾百筆計時紀錄)時,讀寫操作會直接卡住瀏覽器的畫面渲染,造成使用時會有卡頓感。
原生支援 JSON 物件:目前的資料結構包含了主任務、子任務以及陣列形式的 records(計時紀錄)。LocalStorage 每次都必須手動 JSON.stringify 與 JSON.parse,而 IndexedDB 支援 NoSQL 形式的 Object Store,可以直接存取物件。
未來的擴充性:隨著紀錄越來越多,甚至未來加入圖表統計分析時,IndexedDB 的索引(Index)高效率地進行範圍查詢。
在之前的開發,記憶體(Memory)中使用了一個 tasks 陣列來維護全域狀態:
// MissionTimer 的核心資料結構
let tasks = [
{
id: "task-1",
title: "iThome 2026鐵人賽",
subtasks: [
{
id: "sub-1",
title: "主題發想、描述",
status: "completed",
records: [
{
id: "rec-1",
timeRange: "2026/9/14 22:25~22:45",
durationMinutes: 20,
note: "試用市面主流產品和自身需求的比較",
}
],
}
]
}
];
在接下來的兩天中,將會建立一個 TaskTimerDB 的本地資料庫,並建立名為 tasks 的 Object Store(相當於關聯式資料庫的 Table),將這套結構完好地持久化到使用者的瀏覽器中。
(參考資料:Explain This - 本地優先 (local-first) 的軟體設計)
(參考資料:Mdn - IndexedDB API)