現在我們已經可以讓光珠被採集至玻璃瓶中,但如果刷新了頁面,裡面的光珠就會馬上消失,沒有辦法真的儲存下來或是被記得,所以今天要說明 LocalStorage 並使用它來處理這個問題。
在沒有串接後端資料庫的情況下,網頁中的 JavaScript 變數(例如我們的 gems = ref([]))都是暫存在瀏覽器的記憶體裡。一旦關閉分頁或按下重新整理,記憶體被釋放,所有採集好的光珠就會瞬間消失,像是沒存在過。
為了把資料保留在使用者自己的電腦上,瀏覽器提供了原生儲存機制:Web Storage API,其中最常用的就是 LocalStorage。
你可以把 LocalStorage 想像成瀏覽器提供給每個網站專屬的「本地小抽屜」:
既然 LocalStorage 只能存純文字,那我們該怎麼把裝滿光珠物件的陣列 [ { id: 1, note: '...', color: '...' }, ... ] 存進去?
這時候就需要透過 JavaScript 內建的 JSON 工具來進行「打包」與「拆封」:
JSON.stringify()(打包成字串)當我們要存入光珠陣列時,必須先用 JSON.stringify() 把 JavaScript 的陣列物件轉換成符合格式的純文字字串:
const gemsArray = [{ id: 1, note: '散步吹風很舒服', color: '#E3B341' }]
const stringData = JSON.stringify(gemsArray)
// 存進 LocalStorage,標籤取名為 'store_gems'
localStorage.setItem('store_gems', stringData)
JSON.parse()(還原回物件陣列)當下次打開網頁要讀取時,從 LocalStorage 拿出來的只是一串文字,必須透過 JSON.parse() 把字串「解壓縮」變回 Vue 看得懂的陣列:
const savedData = localStorage.getItem('store_gems')
// 如果storage裡面有東西,就解析還原;如果是空的,就給一個空陣列 []
const gemsList = savedData ? JSON.parse(savedData) : []
理解了原理後,接回 Vue 3 的架構其實非常簡單,我們不需要在每次點擊「採集按鈕」時手動寫一次存檔,而是交給 Vue 的響應式與監聽機制。
打開 src/components/GemJar.vue,在 <script setup> 裡做兩處調整:
改寫原本宣告 gems 的那一行:
// 先到 LocalStorage 抓抓看有沒有以前存過的光珠
const savedGems = localStorage.getItem('store_gems')
// 如果有存檔就解析出來當預設值,沒有就初始化為空陣列
const gems = ref(savedGems ? JSON.parse(savedGems) : [])
watch 深度監聽引入 Vue 的 watch,只要 gems 陣列有任何變動(無論是新放入光珠,還是未來要修改內容),就自動把最新陣列轉成字串存進 LocalStorage:
import { ref, watch } from 'vue'
// 監聽 gems 陣列
watch(
gems,
(newGems) => {
localStorage.setItem('store_gems', JSON.stringify(newGems))
},
{ deep: true } // 深度監聽:物件內部的屬性改變也能抓到
)
為什麼要加
{ deep: true }?gems是一個包含多個物件的陣列。如果沒有加上deep: true,Vue 預設只會在陣列本身的長度改變(例如 push、splice)時觸發監聽;未來如果我們直接修改某顆光珠裡面的文字或顏色時,就可能無法觸發存檔。開啟「深度監聽」可以確保陣列內部的任何修改都會自動同步進 LocalStorage。
存檔後回到瀏覽器測試:
store_gems,而 Value 則完整儲存著我們轉成字串的光珠資料。點我看影片:本地儲存成果影片展示
JSON.stringify()(物件轉字串)與 JSON.parse()(字串還原物件)的轉換關鍵。watch 搭配 { deep: true } 實現自動持久化同步,不用在每個按鈕事件裡手動呼叫儲存函式。現在光珠已經可以永久安全地留在瓶子裡了!
但日記的儀式感來自於「每天的記錄」。如果不小心打錯字想反悔怎麼辦?
明天(Day 15),我們要實作「當日微光修改與過期鎖定」,替光珠加上日期標籤,今天放進去的微光允許重新編輯或撤回,但過了今天,它就會化作唯讀的珍貴回憶,被鎖定住無法更改。