一、UniApp 緩存系統概述

1.1 緩存概念與作用

在 UniApp 中,本地緩存(Storage)是一種鍵值對(Key-Value)存儲機制,用於在設備本地保存小量數據。它類似於瀏覽器的 localStorage 或小程序的 wx.setStorage,但 UniApp 通過統一的 API 封裝,實現了跨平台的透明使用。緩存的作用主要體現在以下方面:

  • 性能提升:避免重複網絡請求。例如,登錄後將 Token 存入緩存,下次啓動 App 時直接讀取,無需重新驗證。
  • 用户體驗優化:支持離線模式,如緩存文章列表,用户無網也能瀏覽。
  • 數據持久化:應用重啓或頁面刷新後,數據不丟失(除非手動清理)。
  • 狀態共享:跨頁面/組件共享數據,如購物車內容。

與內存狀態(如 Vuex)不同,緩存是持久化的,但需注意其非關係型特性:不支持複雜查詢,僅適合簡單對象或字符串。在實際項目中,我經常用它來處理臨時配置,避免每次啓動都從服務器拉取。

1.2 平台差異詳解

UniApp 緩存在不同平台的底層實現有所差異,這直接影響存儲上限、持久性和清理策略。以下表格總結關鍵差異:

平台

底層實現

存儲上限

持久性

清理機制

H5

localStorage

約 5MB(瀏覽器限制)

緩存概念,可能被瀏覽器清理

用户清除瀏覽數據或過期

App

plus.storage

無限制(設備存儲空間)

持久化,直至卸載 App

手動清理或系統空間不足

微信小程序

wx.setStorage

單 key 1MB,總 10MB

與小程序生命週期綁定

用户刪除小程序或超限自動清理

支付寶小程序

my.setStorage

單條 200KB,總 10MB

與小程序生命週期綁定

超限自動清理

百度/抖音小程序

平台特定 API

視平台文檔(約 10MB)

與小程序生命週期綁定

平台策略清理

HarmonyOS

分佈式存儲

視設備(HBuilderX 4.23+ 支持)

持久化

手動或系統管理

注意:清空緩存後,非 App 平台可能導致 uni.getSystemInfo 的 deviceId 改變,影響設備標識。開發者需在多端測試,確保兼容。在我的經驗中,App 端的無限制存儲特別適合緩存大文件列表,但要警惕設備空間耗盡。

1.3 支持的數據類型與限制

UniApp 緩存支持原生類型(String、Number、Boolean、Array、Object)和可通過 JSON.stringify 序列化的複雜對象。但不支持函數、Date 對象(需轉為字符串)或循環引用。限制包括:

  • Key 命名:避免系統保留前綴(如 uni-dcloud_),否則可能衝突或失敗。
  • 數據大小:超出平台上限時,存儲失敗,無自動壓縮。
  • 線程安全:異步 API 不阻塞 UI 線程,同步 API 可能在高頻操作時影響性能。
  • 生命週期:H5/App 持久,小程序隨應用銷燬。

這些基礎認知是上手的關鍵,接下來我們深入 API 層面。

二、基礎 API 詳解

UniApp 提供 5 對核心 API:存儲、獲取、移除、清空和信息查詢。每對均有異步(回調式)和同步(try-catch 式)版本。異步適合複雜場景,避免阻塞;同步適合簡單操作,代碼簡潔。以下逐一拆解。

2.1 存儲數據:uni.setStorage / uni.setStorageSync

原理:將數據存入指定 key,會覆蓋原有內容。底層序列化為字符串存儲。

異步版本:uni.setStorage(OBJECT)

參數表:

參數名

類型

必填

説明

key

String


緩存鍵名,建議使用描述性命名如 ‘user_token’

data

Any


存儲內容,支持 JSON 序列化對象

success

Function


成功回調,無參數

fail

Function


失敗回調,參數為錯誤對象

complete

Function


結束回調,無論成敗

返回值:無,通過回調處理。

示例(Vue 頁面中):

// pages/index/index.vue
export default {
methods: {
asyncStoreData() {
uni.setStorage({
key: 'user_info',
data: { id: 1, name: '張三', token: 'abc123' },
success: () => {
console.log('存儲成功');
uni.showToast({ title: '數據已緩存' });
},
fail: (err) => {
console.error('存儲失敗:', err);
uni.showToast({ title: '存儲出錯', icon: 'none' });
}
});
}
}
}

解釋:在按鈕點擊事件中調用,成功後提示用户。data 對象自動序列化。在實際開發中,這種異步方式特別適合用户交互密集的頁面。

同步版本:uni.setStorageSync(key, data)

參數:key (String,必填),data (Any,必填)。

返回值:無,使用 try-catch 處理異常。

示例:

try {
uni.setStorageSync('user_info', { id: 1, name: '張三', token: 'abc123' });
console.log('同步存儲成功');
} catch (e) {
console.error('同步存儲失敗:', e);
}

規則

  • 異步優先:UI 交互場景用異步,避免卡頓。
  • 錯誤處理:fail 回調捕獲網絡/權限問題,try-catch 捕獲序列化失敗。
  • 注意事項:data 必須可序列化,否則拋錯(如包含函數)。我通常在存儲前添加一個序列化檢查函數,確保穩定性。

2.2 獲取數據:uni.getStorage / uni.getStorageSync

原理:從 key 讀取內容,若不存在返回 undefined 或空值。

異步版本:uni.getStorage(OBJECT)

參數表:

參數名

類型

必填

説明

key

String


緩存鍵名

success

Function


回調,res = { data: 讀取內容 }

fail

Function


失敗回調

complete

Function


結束回調

success 返回值:{ data: Any }

示例:

asyncGetData() {
uni.getStorage({
key: 'user_info',
success: (res) => {
if (res.data) {
console.log('獲取數據:', res.data.name);
this.userName = res.data.name;  // 更新頁面數據
} else {
console.log('緩存為空');
}
},
fail: (err) => {
console.error('獲取失敗:', err);
}
});
}

解釋:讀取後直接綁定到頁面 data,實現響應式更新。在組件 mounted 鈎子中調用,能快速初始化視圖。

同步版本:uni.getStorageSync(key)

參數:key (String,必填)。

返回值:Any(不存在時 undefined)。

示例:

try {
const userInfo = uni.getStorageSync('user_info');
if (userInfo) {
console.log('同步獲取:', userInfo.name);
} else {
console.log('無數據');
}
} catch (e) {
console.error('同步獲取失敗:', e);
}

規則

  • 默認值處理:讀取前可檢查 typeof res.data !== ‘undefined’。
  • 性能提示:高頻讀取用同步,低頻用異步。同步版本在初始化階段特別高效,能節省幾毫秒加載時間。

2.3 移除數據:uni.removeStorage / uni.removeStorageSync

原理:刪除指定 key,若不存在仍返回成功。

異步版本:uni.removeStorage(OBJECT)

參數表:

參數名

類型

必填

説明

key

String


要移除的鍵名

success

Function


成功回調,無參數

fail

Function


失敗回調

complete

Function


結束回調

示例:

removeData() {
uni.removeStorage({
key: 'user_info',
success: () => {
console.log('移除成功');
uni.showToast({ title: '緩存已清除' });
}
});
}

同步版本:uni.removeStorageSync(key)

示例:

try {
uni.removeStorageSync('user_info');
console.log('同步移除成功');
} catch (e) {
console.error('移除失敗:', e);
}

注意事項:移除後,相關頁面需刷新數據源,避免顯示舊值。結合事件總線,能通知其他組件更新。

2.4 清空數據:uni.clearStorage / uni.clearStorageSync

原理:移除所有緩存鍵值對,慎用!

異步版本:uni.clearStorage(OBJECT)

參數:success/fail/complete(可選)。

示例:

clearAll() {
uni.clearStorage({
success: () => {
console.log('全部清空成功');
// 跳轉登錄頁
uni.reLaunch({ url: '/pages/login/login' });
}
});
}

同步版本:uni.clearStorageSync()

示例:

try {
uni.clearStorageSync();
console.log('同步清空成功');
} catch (e) {
console.error('清空失敗:', e);
}

規則

  • 備份重要數據:清空前用 getStorageInfo 導出 keys。
  • 平台影響:非 App 端可能重置 deviceId。在生產環境中,我會添加確認對話框,防止誤操作。

2.5 獲取存儲信息:uni.getStorageInfo / uni.getStorageInfoSync

原理:查詢當前緩存狀態,用於監控和調試。

異步版本:uni.getStorageInfo(OBJECT)

success 返回值:

參數名

類型

説明

keys

Array

所有鍵名數組

currentSize

Number

當前佔用 KB

limitSize

Number

限制 KB

示例:

getInfo() {
uni.getStorageInfo({
success: (res) => {
console.log('鍵列表:', res.keys);
console.log('佔用:', res.currentSize + 'KB / ' + res.limitSize + 'KB');
}
});
}

同步版本:uni.getStorageInfoSync()

示例:

try {
const res = uni.getStorageInfoSync();
console.table(res);  // 表格輸出
} catch (e) {
console.error(e);
}

應用:在設置頁顯示存儲使用率,提示用户清理。定期調用能幫助診斷內存問題。

通過這些基礎 API,開發者可快速實現簡單緩存。接下來,我們探討如何在複雜場景中擴展它們。

三、高級用法與技巧

基礎 API 雖強大,但實際項目中需處理過期、集成和安全等問題。本節提供一些實用技巧,幫助你構建更robust的緩存系統。

3.1 緩存過期機制實現

原理:UniApp 無內置過期,需手動添加時間戳判斷。

實現步驟:

  1. 存儲時附加 timestamp: Date.now()。
  2. 讀取時計算差值,超閾值則移除並返回 null。

示例(工具函數):

// utils/cache.js
const CACHE_EXPIRE = 60 * 60 * 1000;  // 1小時
export function setWithExpire(key, data, expire = CACHE_EXPIRE) {
const cacheObj = {
data,
timestamp: Date.now() + expire
};
try {
uni.setStorageSync(key, JSON.stringify(cacheObj));
} catch (e) {
console.error('設置過期緩存失敗:', e);
}
}
export function getWithExpire(key) {
try {
const str = uni.getStorageSync(key);
if (!str) return null;
const cacheObj = JSON.parse(str);
if (Date.now() > cacheObj.timestamp) {
uni.removeStorageSync(key);  // 過期移除
return null;
}
return cacheObj.data;
} catch (e) {
console.error('獲取過期緩存失敗:', e);
return null;
}
}

使用

// 存儲
setWithExpire('article_list', articles, 30 * 60 * 1000);  // 30分鐘
// 讀取
const articles = getWithExpire('article_list');
if (articles) {
this.list = articles;
} else {
// 從服務器重新獲取
}

優勢:防止陳舊數據,提升數據新鮮度。在電商 App 中,我用它緩存商品價格,過期後自動刷新,避免用户看到過時信息。

3.2 與 Vuex/Pinia 集成持久化

原理:Vuex 內存狀態非持久,結合緩存實現重啓恢復。

Vuex 示例(store/index.js):

import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
user: null,
cart: []
},
mutations: {
SET_USER(state, user) {
state.user = user;
// 持久化
uni.setStorageSync('vuex_user', JSON.stringify(user));
},
SET_CART(state, cart) {
state.cart = cart;
uni.setStorageSync('vuex_cart', JSON.stringify(cart));
}
},
actions: {
initFromCache({ commit }) {
// 應用啓動時恢復
const userStr = uni.getStorageSync('vuex_user');
if (userStr) commit('SET_USER', JSON.parse(userStr));
const cartStr = uni.getStorageSync('vuex_cart');
if (cartStr) commit('SET_CART', JSON.parse(cartStr));
}
}
});
export default store;

main.js 中調用

import store from './store';
Vue.prototype.$store = store;
store.dispatch('initFromCache');  // App 啓動時執行

Pinia 變體:類似,使用 defineStore 的 persist 插件(社區擴展)。

規則:僅持久關鍵狀態,避免緩存大對象。在大型項目中,這種集成能讓狀態管理更可靠,用户重啓 App 後無縫繼續。

3.3 全局緩存管理工具封裝

原理:統一入口,簡化多處調用,支持命名空間。

示例(utils/storageManager.js):

class StorageManager {
constructor(namespace = '') {
this.namespace = namespace ? `${namespace}_` : '';
}
set(key, data) {
try {
uni.setStorageSync(this.namespace + key, data);
} catch (e) {
throw new Error(`存儲失敗: ${e.message}`);
}
}
get(key, defaultValue = null) {
try {
return uni.getStorageSync(this.namespace + key) || defaultValue;
} catch (e) {
console.warn(`獲取失敗: ${key}`);
return defaultValue;
}
}
remove(key) {
try {
uni.removeStorageSync(this.namespace + key);
} catch (e) {}
}
clear() {
try {
uni.clearStorageSync();
} catch (e) {}
}
getInfo() {
try {
return uni.getStorageInfoSync();
} catch (e) {
return null;
}
}
}
// 使用
const userStorage = new StorageManager('user');
userStorage.set('token', 'xyz');
const token = userStorage.get('token');

優勢:避免 key 衝突,支持模塊化(如 ‘user_token’)。在團隊開發中,這樣的封裝能減少代碼重複,提高維護性。

3.4 數據加密存儲

原理:敏感數據(如 Token)易被逆向,需 AES 加密。

依賴:UniApp 無內置 crypto,App 端用 plus.crypto(需條件編譯)。

示例(簡單 Base64 + 自定義密鑰,H5/小程序通用):

// utils/encrypt.js
const KEY = 'MySecretKey12345';  // 生產環境用隨機密鑰
function encrypt(data) {
const str = typeof data === 'object' ? JSON.stringify(data) : data;
let result = '';
for (let i = 0; i < str.length; i++) {
result += String.fromCharCode(str.charCodeAt(i) ^ KEY.charCodeAt(i % KEY.length));
}
return btoa(result);  // Base64 編碼
}
function decrypt(encrypted) {
try {
const decoded = atob(encrypted);
let result = '';
for (let i = 0; i < decoded.length; i++) {
result += String.fromCharCode(decoded.charCodeAt(i) ^ KEY.charCodeAt(i % KEY.length));
}
return JSON.parse(result);  // 假設對象
} catch (e) {
return null;
}
}
// 使用
uni.setStorageSync('encrypted_token', encrypt('abc123'));
const token = decrypt(uni.getStorageSync('encrypted_token'));

注意:H5 用 CryptoJS 庫(npm 引入),App 用原生。對於金融類 App,這層保護至關重要,能防範簡單逆向工程。

3.5 批量操作與事務模擬

原理:無原生事務,用 Promise.all 模擬批量。

示例:

async batchSet(items) {
const promises = items.map(({ key, data }) =>
new Promise((resolve, reject) => {
uni.setStorage({
key,
data,
success: resolve,
fail: reject
});
})
);
try {
await Promise.all(promises);
console.log('批量存儲成功');
} catch (e) {
console.error('批量失敗,回滾');
// 模擬回滾:清空相關鍵
items.forEach(({ key }) => uni.removeStorageSync(key));
}
}
// 調用
this.batchSet([
{ key: 'config1', data: { theme: 'dark' } },
{ key: 'config2', data: { lang: 'zh' } }
]);

規則:失敗時回滾,確保一致性。適用於配置批量更新,在初始化多模塊時很實用。

四、實際應用場景

本節通過 4 個典型場景,展示緩存的實戰價值。每場景包含需求、實現和優化。

4.1 用户登錄狀態管理

需求:登錄後保存 Token 和用户信息,退出時清除;重啓 App 自動驗證。

實現

  • 登錄頁(login.vue):
login() {
// 模擬 API
const user = { id: 1, name: '李四', token: 'def456' };
uni.setStorageSync('user_token', user.token);
uni.setStorageSync('user_info', JSON.stringify(user));
uni.switchTab({ url: '/pages/home/home' });
}
  • Home 頁(home.vue):
onLoad() {
const token = uni.getStorageSync('user_token');
if (token) {
const userStr = uni.getStorageSync('user_info');
this.user = userStr ? JSON.parse(userStr) : null;
} else {
uni.reLaunch({ url: '/pages/login/login' });
}
},
methods: {
logout() {
uni.removeStorageSync('user_token');
uni.removeStorageSync('user_info');
uni.reLaunch({ url: '/pages/login/login' });
}
}

優化:集成過期(3.1),每 7 天重登。

在實際項目中,可添加 JWT 解析驗證 Token 有效性:

// 驗證 Token
function isTokenValid(token) {
try {
const payload = JSON.parse(atob(token.split('.')[1]));
return Date.now() < payload.exp * 1000;
} catch (e) {
return false;
}
}

這確保安全,防止過期 Token 誤用。在社交 App 中,這種機制讓用户體驗更流暢。

4.2 配置信息持久化

需求:保存主題色、語言等設置,重啓生效。

實現

  • 設置頁(setting.vue):
changeTheme(color) {
uni.setStorageSync('app_theme', color);
// 動態更新全局樣式(需 CSS 變量)
const dom = document.documentElement;
dom.style.setProperty('--theme-color', color);
}
onLoad() {
const savedTheme = uni.getStorageSync('app_theme') || '#007AFF';
// 應用主題
}

優化:用 getStorageInfo 監控,超 1MB 提示清理。結合 Vuex(3.2),實現響應式切換。

擴展:多語言支持:

// 存儲語言包
uni.setStorageSync('lang_pack', { zh: { hello: '你好' }, en: { hello: 'Hello' } });
const lang = uni.getStorageSync('current_lang') || 'zh';
this.messages = uni.getStorageSync('lang_pack')[lang];

這在國際化 App 中實用,減少 bundle 大小。用户切換語言時,立即生效,無需重啓。

4.3 離線數據緩存

需求:緩存 API 返回的文章列表,支持無網瀏覽。

實現(使用 3.1 過期):

// 獲取列表
async fetchArticles() {
const cached = getWithExpire('articles');
if (cached) {
this.articles = cached;
return;
}
try {
const res = await uni.request({ url: '/api/articles' });
setWithExpire('articles', res.data, 24 * 60 * 60 * 1000);  // 24小時
this.articles = res.data;
} catch (e) {
console.error('網絡錯誤,使用緩存');
this.articles = cached || [];
}
}

優化:分頁緩存,如 ‘articles_page_1’。添加網絡狀態檢測:

uni.getNetworkType({
success: (res) => {
if (res.networkType === 'none') {
// 僅用緩存
}
}
});

這提升了魯棒性,在弱網環境閃光。新聞類 App 中,用户地鐵上也能閲讀緩存內容。

4.4 列表分頁緩存

需求:電商商品列表,分頁加載,緩存每頁數據。

實現

loadPage(page = 1) {
const key = `goods_page_${page}`;
const cached = uni.getStorageSync(key);
if (cached) {
this.goods = [...this.goods, ...cached];
return;
}
uni.request({
url: `/api/goods?page=${page}`,
success: (res) => {
uni.setStorageSync(key, res.data);
this.goods = [...this.goods, ...res.data];
}
});
}

優化:預加載下一頁,移除舊頁(如 >10 頁清空)。結合 getStorageInfo,避免總大小超限。

擴展討論:在高併發場景,可用 IndexedDB(H5 專用,條件編譯)補充大列表緩存:

<script>
import { openDB } from 'idb';  // npm idb
const db = await openDB('goods_db', 1, {
  upgrade(db) {
    db.createObjectStore('pages');
  }
});
await db.put('pages', data, `page_${page}`);
</script>

這在 Web 端處理海量數據時高效,結合緩存層,能實現混合存儲策略。

五、性能優化與最佳實踐

5.1 同步 vs 異步選擇

  • 異步優先:95% 場景用回調/Promise,避免 UI 阻塞。示例:onLoad 中異步 get。
  • 同步場景:啓動時快速恢復,或簡單工具函數。
  • 最佳實踐:封裝 Promise 化異步:
function setStoragePromise(key, data) {
return new Promise((resolve, reject) => {
uni.setStorage({
key, data,
success: resolve,
fail: reject
});
});
}
// 使用 await setStoragePromise('key', data);

在我的項目中,統一用 Promise,能讓 async/await 代碼更優雅。

5.2 存儲大小管理

  • 監控:定期調用 getStorageInfo,若 currentSize > 80% limitSize,清理舊數據。
  • 壓縮:大對象用 lz-string 庫壓縮(npm 引入)。
  • 實踐:設置頁添加“清理緩存”按鈕:
clearOld() {
const res = uni.getStorageInfoSync();
if (res.currentSize > res.limitSize * 0.8) {
// 移除 7 天前數據(結合 3.1)
res.keys.forEach(key => {
if (getWithExpire(key) === null) uni.removeStorageSync(key);
});
}
}

定期清理能保持 App 輕量,尤其在低端設備上。

5.3 錯誤處理與調試

  • 統一日誌:用 console.error + uni.reportEvent 上報。
  • 調試工具:HBuilderX 控制枱查看 Storage,Chrome DevTools 檢查 H5 localStorage。
  • 實踐:添加 try-catch 包裝所有操作,fail 回調 toast 提示。遇到序列化錯誤時,fallback 到字符串存儲。

5.4 跨平台兼容

  • 條件編譯:#ifdef APP 用 plus.storage 擴展。
  • 測試:多端運行,關注小程序審核(如支付寶 10MB 限)。
  • 實踐:manifest.json 中配置 storage 權限。在 CI/CD 中集成多端測試腳本。

5.5 清理策略

  • 自動:應用退出時 clear(可選)。
  • 手動:用户設置頁一鍵清。
  • 智能:基於使用頻率,LRU(最近最少用)算法移除(自定義 Map 實現)。

示例 LRU:

class LRUCache {
constructor(maxSize) {
this.maxSize = maxSize;
this.cache = new Map();
}
set(key, value) {
if (this.cache.has(key)) this.cache.delete(key);
this.cache.set(key, value);
if (this.cache.size > this.maxSize) {
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
}
get(key) {
if (this.cache.has(key)) {
const value = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key, value);
return value;
}
return null;
}
}

用它包裝 Storage,能智能管理熱數據。

六、常見問題與解決方案

6.1 常見錯誤

  • 序列化失敗:data 含不可序列化類型。
    :預檢查 JSON.stringify(data) 不拋錯。
  • Key 衝突:用系統前綴。
    :命名規範,如 ‘app_user_token’。
  • 讀取 undefined:key 不存在。
    :默認值 || {}

6.2 平台特定問題

  • H5 緩存丟失:瀏覽器隱私模式。
    :fallback 到 sessionStorage。
  • 小程序超限:微信 10MB。
    :分 key 存儲,定期 getStorageInfo 檢查。
  • App 無限制濫用:佔用設備空間。
    :設置軟上限 50MB,自行清理。

6.3 調試技巧

  • 打印 keys:循環 getStorageSync(key) 查看內容。
  • 模擬清理:clearStorageSync() + 重載。
  • 工具:UniApp 插件“Storage Viewer”。在開發時,添加一個調試模式,暴露所有 keys 到控制枱。

七、擴展與進階

7.1 與其他存儲結合

  • SQLite(App 端):複雜數據用 uni.requireNativePlugin(‘sqlite’)。
    示例:用户訂單表,緩存僅存 ID 列表。
  • IndexedDB(H5):大文件緩存。
    實踐:圖片縮略圖存 IndexedDB,詳情存 Storage。混合使用能平衡性能和容量。

7.2 雲端同步

  • 原理:本地變更時上傳雲(uniCloud),拉取時合併。
  • 示例:登錄後 syncCacheToCloud(),用 diff 算法避免覆蓋。
async syncCache() {
const localKeys = uni.getStorageInfoSync().keys;
const cloudData = await uniCloud.callFunction({ name: 'getCache' });
localKeys.forEach(key => {
if (!cloudData.has(key)) {
uniCloud.callFunction({ name: 'uploadCache', data: { key, value: uni.getStorageSync(key) } });
}
});
}

這在多設備同步場景中強大,如筆記 App。

7.3 自定義緩存模塊

  • 基於 Redux-like:狀態 + reducer + storage middleware。
  • 進階:支持 RxJS 響應式緩存更新。示例:訂閲緩存變化,自動 UI 刷新。
// 用 RxJS
import { fromEvent } from 'rxjs';
const storageEvent = fromEvent(window, 'storage');
storageEvent.subscribe(event => {
if (event.key === 'my_key') {
// 更新狀態
}
});

這讓緩存成為響應式數據源。