iT邦幫忙

2026 iThome 鐵人賽

DAY 26
0
Modern Web

從 UI/UX 設計到前端實作:30 天打造專屬的任務學習計時器系列 第 26 篇

DAY 26|資料被清掉怎麼辦?使用JSON 資料備份與還原

  • 分享至 

  • xImage
  •  

前幾天我們完成了 IndexedDB 的本地資料儲存,在沒有後端 API 與伺服器資料庫的情況下,依然能順暢地在瀏覽器端運作與記錄資料。

但是,雖然 Local-First(本地優先)架構帶來順暢使用與保有隱私,一旦使用者手動清理瀏覽器快取、開啟無痕模式,或是更換電腦與手機,原本記錄的任務資料就會消失。

因此為了避免這樣的問題,我決定增加資料的匯入匯出功能,讓使用者可以視需求備份,如果之後轉換設備,或是清除瀏覽器快取後,可以自行重新匯入資料。


核心運作機制與流程設計

JSON(JavaScript Object Notation) 是 Web 開發中最標準的資料交換格式。將資料匯出與匯入的運作流程可拆解為以下步驟:

資料匯出流程(Export)

  1. 讀取資料:從應用程式狀態(或 IndexedDB)中取出當前所有的任務與紀錄資料。

  2. 包裝結構:除了任務陣列外,額外附帶應用程式版本號(version)與匯出時間戳記(exportedAt)。

  3. 物件轉字串:使用 JSON.stringify() 將 JavaScript 物件轉換為標準 JSON 字串。

  4. 建立 Blob 與下載連結:將 JSON 字串封裝成 Blob 物件,並透過 URL.createObjectURL() 產生臨時下載網址,以動態 <a> 標籤觸發瀏覽器下載檔案。

  5. 記憶體釋放:下載觸發完畢後,即時呼叫 URL.revokeObjectURL() 釋放佔用的記憶體空間。


資料匯入與還原流程(Import)

  1. 觸發檔案選擇:透過隱藏的 <input type="file" accept=".json"> 讓使用者選擇備份檔。

  2. 非同步讀取檔案:使用 JavaScript 原生的 FileReader API 讀取 JSON 檔案內容。

  3. 防呆與格式驗證(Data Validation):將 JSON 字串解析為物件後,檢查資料結構是否符合規範(例如是否包含關鍵欄位 id 與 title),避免使用者誤匯入無效或損壞的檔案。

  4. 覆蓋二次確認:跳出警告彈窗(confirm),提醒使用者還原動作將會覆蓋當前資料。

  5. 狀態同步與持久化:驗證無誤後更新全域狀態,同時寫回 IndexedDB 並重新渲染畫面。


核心邏輯與實作解析

以下為匯入與匯出邏輯的核心程式碼結構:

1. JSON 匯出邏輯

function exportDataToJSON() {
  if (!tasks || tasks.length === 0) return;

  // 1. 建立包含版本號與時間戳記的封包結構
  const backupData = {
    version: "1.0.0",
    exportedAt: new Date().toISOString(),
    data: tasks
  };

  // 2. 轉為 JSON 字串並建立 Blob
  const jsonString = JSON.stringify(backupData, null, 2);
  const blob = new Blob([jsonString], { type: "application/json" });
  const downloadUrl = URL.createObjectURL(blob);

  // 3. 建立動態連結觸發下載
  const downloadLink = document.createElement("a");
  downloadLink.href = downloadUrl;
  downloadLink.download = `TaskTimer_Backup_${new Date().toISOString().split("T")[0]}.json`;
  downloadLink.click();

  // 4. 釋放記憶體
  URL.revokeObjectURL(downloadUrl);
}

語法拆解:

  • 後設資料封裝 (version & exportedAt):將 tasks 陣列包裹在物件中,並補上 version 與 exportedAt。方便系統判斷備份檔的版號。

  • 美化縮排 (JSON.stringify(..., null, 2)):加上 null, 2 參數能輸出排版漂亮的 JSON 格式,方便使用者下載後打開檢視或除錯。

  • Blob 與 URL.createObjectURL:Blob 將 JSON 字串封裝為瀏覽器可辨識的二進位物件,再透過 createObjectURL 產生一組臨時的下載 URL(blob:http://...)。

  • 虛擬 <a> 標籤下載法:透過 DOM 動態建立 <a download="..."> 並執行 .click(),不需跳轉頁面即可自動觸發瀏覽器下載檔案,檔名並以當日日期 (YYYY-MM-DD) 命名。

  • 記憶體管理 (URL.revokeObjectURL):觸發下載後立即呼叫 revokeObjectURL() ,釋放剛才產生的 Blob URL,避免長時間佔用瀏覽器記憶體。


JSON 匯入與安全驗證

匯入功能是使用者復原資料的核心,必須考慮相容性、格式驗證與使用者二次確認:

function importDataFromJSON(file) {
  if (!file) return;

  const reader = new FileReader();

  reader.onload = async (e) => {
    try {
      const parsedData = JSON.parse(e.target.result);
      const targetTasks = Array.isArray(parsedData) ? parsedData : parsedData.data;

      // 簡單的資料結構校驗 (Validation)
      const isValid = targetTasks?.every(t => t.id && t.title);
      if (!isValid) throw new Error("資料格式不符");

      if (confirm("還原備份將會覆蓋現有資料,確定要繼續嗎?")) {
        tasks = targetTasks;
        await syncAndRender(); // 重新渲染並同步至 IndexedDB
      }
    } catch (error) {
      alert("檔案讀取失敗,請確認是否為正確的 JSON 備份檔!");
    }
  };

  reader.readAsText(file);
}

語法拆解:

  • FileReader 非同步讀取:使用 HTML5 的 FileReader API,透過 readAsText(file) 非同步讀取使用者選擇的檔案,並在 onload 事件中回傳檔案內容。

  • 向下相容性(Backward Compatibility):

  • Array.isArray(parsedData) ? parsedData : parsedData.data

  • 寫法同時相容「純陣列的舊版 JSON」與「帶有版本號的 backupData.data 新封包」,確保舊使用者的備份檔依然能順利還原。

  • 嚴謹的資料結構校驗 (Validation):使用 Array.prototype.every() 檢查陣列中的每一筆物件是否都具備必填欄位(id 與 title),避免匯入格式不對的亂碼或不完整資料導致系統崩潰。

  • 破壞性操作防護 (confirm):覆蓋資料屬於不可逆的破壞性操作,在覆寫記憶體與 IndexedDB 前彈出對話框進行二次確認,確保使用者操作安全。

  • 錯誤捕捉 (try...catch):若使用者傳入非 JSON 格式的檔案(例如 .txt 或 .png),JSON.parse() 會給予友善的錯誤提示。


上一篇
DAY 25|資料備份:資料主權歸於使用者,實作 CSV 報表匯出功能
系列文
從 UI/UX 設計到前端實作:30 天打造專屬的任務學習計時器 共 26 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言