Skip to content

移動端頁面狀態契約:載入、離線與過期資料如何共存

一個 isLoading 布爾值不足以描述真實移動頁面。頁面可能正在更新,但舊資料仍可讀;也可能請求成功卻為空;斷網時有快取與沒有快取,使用者應該看到完全不同的內容。

用資料和同步狀態兩個維度建模 ​

保留 snapshot、lastSyncedAt、requestState、error。渲染規則從這些欄位推導,不把每一種組合都塞進一個巨大的枚舉。這樣「舊資料 + 更新中」和「舊資料 + 更新失敗」都能保留頁面主體,同時展示不同提示。

快照請求頁面表現
無首次載入骨架屏,保留回傳操作
無失敗錯誤說明與重試入口
有更新中繼續展示快照,局部更新提示
有失敗展示快照、同步時間和非阻斷錯誤
空清單成功真正的空態和建立入口

把狀態規則放在視圖外 ​

在 RN 的 selector 或 Flutter 的 ViewModel 中實現純函數 deriveScreenState(snapshot, request),讓多個頁面共享規則。進入頁面、下拉更新、前景恢復、推送消息觸發更新,都調用同一資料入口;每個入口只改變請求原因,不改變「什麼是可信快照」。

驗收時模擬慢網和離線:連續更新不會把清單清空;失敗時不會把舊清單誤判為空;帳戶切換後絕不展示上一帳戶的快照。空態只能由一次可信的成功響應確認。

把“空”變成有證據的狀態 ​

以消息列表為例,首次進入頁面時沒有緩存,狀態是 initialLoading;成功返回零條消息後才是 confirmedEmpty。如果先前有二十條消息,用戶下拉刷新時應保持列表可讀,狀態是 refreshingWithData。刷新失敗則進入 staleWithError,繼續保留二十條和上次成功時間。把所有情況壓成 loading | success | error,會迫使界面在刷新期間清空內容,或把失敗誤畫成空列表。

數據倉庫建議保存 {accountId, items, lastSuccessAt, revision, pending},展示狀態由這些事實和當前請求推導,不要讓每個組件各自維護一套布爾值。items=[] 只表示當前數組為空;只有“當前賬號的一次可信成功響應”才能證明真正空態。賬戶切換時先隔離舊賬號快照,再加載新賬號緩存,避免上一賬號的消息在新賬號頁面閃現。

事件保留的數據頁面反饋
首次加載失敗無錯誤頁與重試按鈕
有緩存時刷新失敗上次確認的列表內聯錯誤、同步時間與重試
請求成功返回空列表當前賬號的空快照明確的空態文案
賬號切換僅新賬號緩存骨架屏或新賬號快照

狀態轉換比截圖更值得測試 ​

以 reducer 或狀態機測試事件序列:成功(20條) → 刷新 → 超時 必須保留二十條;成功(20條) → 成功(0條) 才能變成空態;賬號A加載 → 切換B → A響應 必須丟棄 A 的結果。UI 測試再驗證重試入口、讀屏提示和舊數據標識。這樣測試的是用戶會經歷的變化,而不僅是一張靜態頁面。

MIT Licensed