上一篇的備忘錄裡,我們把輸入內容暫時放進 JavaScript 的資料裡。
例如:
const memos = [];
memos.push("買牛奶");
這時候:
memos
// ["買牛奶"]
看起來資料已經「存起來」了。
但只要重新整理頁面,資料卻又不見了。
這時候我才開始意識到:
畫面上看得到資料,不代表資料真的被長期保存。
所以這篇想搞懂一件事:
一筆資料,到底住在哪裡?
先看:
const memos = [];
接著:
memos.push("買牛奶");
現在:
["買牛奶"]
但重新整理頁面後,JavaScript 會重新執行。
流程可以理解成:
第一次載入
const memos = []
↓
push("買牛奶")
↓
["買牛奶"]
🔄 Reload
JavaScript 重新執行
↓
const memos = []
↓
又回到空 Array
所以資料不是被「刪掉」。
而是:
程式重新從頭執行,我們又重新建立了一個新的空 Array。
如果資料只存在 JavaScript 變數裡,Reload 之後就不會自動幫我們保留下來。
如果希望重新整理後資料還在,可以另外存進瀏覽器的儲存空間。
例如:
localStorage
假設現在有:
const memos = ["買牛奶", "寫文章"];
可以這樣存:
localStorage.setItem(
"memos",
JSON.stringify(memos)
);
這裡可以拆成:
Array
["買牛奶", "寫文章"]
↓ JSON.stringify()
String
'["買牛奶","寫文章"]'
↓ localStorage.setItem()
存進瀏覽器
為什麼需要 JSON.stringify()?
因為 localStorage 儲存的是字串,所以要先把 Array 轉成可以保存的 JSON 字串。
Reload 之後,可以這樣取回:
const savedMemos =
localStorage.getItem("memos");
這時候拿到的 savedMemos 是字串。
例如:
'["買牛奶","寫文章"]'
所以還要再:
const memos = savedMemos
? JSON.parse(savedMemos)
: [];
JSON.parse() 會把 JSON 字串解析回 JavaScript 資料。
完整流程就是:
Array
↓ JSON.stringify()
String
↓ setItem()
localStorage
🔄 Reload
↓ getItem()
String
↓ JSON.parse()
Array
所以:
JSON.stringify()
→ 準備存進去
JSON.parse()
→ 拿回來繼續使用
localStorage 跟 sessionStorage 差在哪?除了 localStorage,還常看到:
sessionStorage
可以先簡單分成:
| 儲存位置 | Reload 後 | 分頁關閉後 |
|---|---|---|
| JavaScript 變數 | 重新建立 | 消失 |
sessionStorage |
還在 | Page session 結束後清除 |
localStorage |
還在 | 不會因一般關閉分頁自動清除 |
但這兩種 Storage 都還是在:
使用者目前的瀏覽器端。
它們跟真正的後端資料庫,還是不同層。
如果今天不是練習用備忘錄,而是真正的系統,資料通常不會只存在某個人的瀏覽器。
例如新增一筆資料時:
使用者操作
↓
Frontend
↓
POST / PATCH Request
↓
Backend
↓
Database
重新整理頁面後:
Frontend
↓
GET Request
↓
Backend
↓
Database
↓
Response
↓
Frontend Render
所以「重新整理後資料還在」可能有兩種完全不同的原因:
原因一
localStorage 裡本來就還有
原因二
Frontend 重新打 API
↓
Backend 從 Database 取回資料
光看畫面,不一定知道資料真正來自哪裡。
後來在實際專案裡,我遇過一類很典型的問題。
使用者修改某個欄位並送出。
從 Network 看:
PATCH Request
↓
Status 200
↓
Request 也確實帶了新的值
第一眼很容易覺得:
「API 成功了,應該沒問題。」
但重新取得資料後:
GET Request
↓
Response
↓
舊值又回來
畫面也跟著恢復原本的內容。
這時候如果只看 UI,很容易一直去改 Input、Form 或 Render。
但把流程拆開之後:
使用者修改
↓
Frontend Form
↓
PATCH Request
↓
Backend
↓
Database
↓
Response
↓
Frontend Render
就可以一層一層確認:
Frontend 送出的值正確嗎?
↓
Request 有真的送出去嗎?
↓
Backend 有接受這個值嗎?
↓
Database 有真的更新嗎?
↓
下一次 Response 回來的是新值還是舊值?
這時候有一個很重要的觀念:
HTTP 200 不代表資料一定已經變成你期待的樣子。
它只代表這次 Request 被成功處理。
真正 Debug 時,還要繼續確認:
資料最後到底有沒有正確保存。
我後來很常提醒自己:
UI
↓
Frontend State / Variable
↓
Browser Storage / Request
↓
Backend
↓
Database
↓
Response
↓
重新 Render
其中任何一層出問題,最後畫面看起來都可能不一樣。
例如:
li.remove();
只代表:
把畫面上的元素移除。
但如果真正的資料還存在 Database,下一次重新取得資料時,它還是可能重新出現。
所以:
畫面消失,不代表資料真的被刪除。
同樣地:
畫面顯示新值,也不代表 Database 一定已經更新。
遇到資料異常時,我會先確認:
這個值現在來自哪裡?
↓
Form?
State?
localStorage?
API Response?
Backend?
Database?
接著再追:
Request 送了什麼?
↓
Response 回了什麼?
↓
最後畫面又使用哪一份資料 Render?
這比單純看到:
200 OK
就判斷「沒問題」,可靠很多。
UI
│
▼
Frontend Data
│
├─ JavaScript Variable
│ └─ Reload 後重新建立
│
├─ localStorage / sessionStorage
│ └─ 儲存在瀏覽器
│
└─ API
↓
Backend
↓
Database
所以以後看到一筆資料,可以多問一句:
它真正的資料來源在哪裡?
這個問題到了 React、Form、API Debug,甚至後面的真實專案裡,都還會一直出現。
下一篇就把同一個概念帶進 React:
同樣是更新一筆資料,為什麼到了 React 會開始出現 useState、onClick 和重新 Render?