uni-app 数据缓存策略

章节介绍

数据缓存是移动应用开发中的重要技术,它可以显著提升应用性能、减少网络请求、降低服务器压力,同时在离线状态下也能提供基本功能。uni-app 提供了多种本地存储 API,支持不同场景的数据缓存需求。本章节将详细介绍 uni-app 中的数据缓存策略,包括本地存储 API 的使用、缓存机制的设计、缓存清理和更新策略等核心知识点,以及如何实现高效的数据缓存系统,帮助你构建性能更优的应用。

核心知识点讲解

1. 本地存储 API

uni-app 提供了多种本地存储 API,适用于不同的数据存储场景。

1. uni.setStorage / uni.setStorageSync

功能:将数据存储在本地缓存中,键名唯一。

参数

  • key:存储的键名
  • data:需要存储的数据
  • success:接口调用成功的回调函数
  • fail:接口调用失败的回调函数
  • complete:接口调用结束的回调函数(无论成功或失败)

示例

// 异步存储
uni.setStorage({
  key: 'userInfo',
  data: { name: '张三', age: 25 },
  success: function() {
    console.log('存储成功');
  },
  fail: function() {
    console.error('存储失败');
  }
});

// 同步存储
try {
  uni.setStorageSync('userInfo', { name: '张三', age: 25 });
  console.log('存储成功');
} catch (error) {
  console.error('存储失败:', error);
}

2. uni.getStorage / uni.getStorageSync

功能:从本地缓存中获取数据。

参数

  • key:存储的键名
  • success:接口调用成功的回调函数
  • fail:接口调用失败的回调函数
  • complete:接口调用结束的回调函数(无论成功或失败)

示例

// 异步获取
uni.getStorage({
  key: 'userInfo',
  success: function(res) {
    console.log('获取成功:', res.data);
  },
  fail: function() {
    console.error('获取失败');
  }
});

// 同步获取
try {
  const userInfo = uni.getStorageSync('userInfo');
  console.log('获取成功:', userInfo);
} catch (error) {
  console.error('获取失败:', error);
}

3. uni.removeStorage / uni.removeStorageSync

功能:从本地缓存中删除指定键名的数据。

参数

  • key:存储的键名
  • success:接口调用成功的回调函数
  • fail:接口调用失败的回调函数
  • complete:接口调用结束的回调函数(无论成功或失败)

示例

// 异步删除
uni.removeStorage({
  key: 'userInfo',
  success: function() {
    console.log('删除成功');
  },
  fail: function() {
    console.error('删除失败');
  }
});

// 同步删除
try {
  uni.removeStorageSync('userInfo');
  console.log('删除成功');
} catch (error) {
  console.error('删除失败:', error);
}

4. uni.clearStorage / uni.clearStorageSync

功能:清除本地缓存中的所有数据。

参数

  • success:接口调用成功的回调函数
  • fail:接口调用失败的回调函数
  • complete:接口调用结束的回调函数(无论成功或失败)

示例

// 异步清除
uni.clearStorage({
  success: function() {
    console.log('清除成功');
  },
  fail: function() {
    console.error('清除失败');
  }
});

// 同步清除
try {
  uni.clearStorageSync();
  console.log('清除成功');
} catch (error) {
  console.error('清除失败:', error);
}

5. uni.getStorageInfo / uni.getStorageInfoSync

功能:获取本地缓存的相关信息。

参数

  • success:接口调用成功的回调函数
  • fail:接口调用失败的回调函数
  • complete:接口调用结束的回调函数(无论成功或失败)

返回值

  • keys:本地缓存中的所有键名
  • currentSize:当前占用的空间大小,单位:KB
  • limitSize:限制的空间大小,单位:KB

示例

// 异步获取
uni.getStorageInfo({
  success: function(res) {
    console.log('所有键名:', res.keys);
    console.log('当前占用空间:', res.currentSize);
    console.log('限制空间:', res.limitSize);
  },
  fail: function() {
    console.error('获取失败');
  }
});

// 同步获取
try {
  const storageInfo = uni.getStorageInfoSync();
  console.log('所有键名:', storageInfo.keys);
  console.log('当前占用空间:', storageInfo.currentSize);
  console.log('限制空间:', storageInfo.limitSize);
} catch (error) {
  console.error('获取失败:', error);
}

2. 缓存机制

1. 缓存类型

根据缓存的生命周期和用途,可以将缓存分为以下几种类型:

  • 临时缓存:应用运行期间的临时数据,不需要持久化存储
  • 会话缓存:用户会话期间的数据,会话结束后可以清除
  • 持久缓存:需要长期保存的数据,如用户配置、离线数据等
  • 版本化缓存:与应用版本关联的缓存,版本更新时需要清理

2. 缓存策略

常见的缓存策略包括:

  • **LRU (Least Recently Used)**:最近最少使用策略,当缓存空间不足时,删除最久未使用的数据
  • **LFU (Least Frequently Used)**:最不经常使用策略,当缓存空间不足时,删除使用频率最低的数据
  • **FIFO (First In First Out)**:先进先出策略,当缓存空间不足时,删除最早存入的数据
  • **TTL (Time To Live)**:生存时间策略,为每个缓存项设置过期时间

3. 缓存工具类

为了更方便地使用本地存储 API,我们可以封装一个缓存工具类,提供更高级的缓存功能。

1. 基础缓存工具类

// utils/storage.js

/**
 * 本地存储工具类
 */
export const storage = {
  /**
   * 存储数据
   * @param {string} key - 存储键名
   * @param {any} value - 存储值
   * @param {number} expire - 过期时间(毫秒)
   */
  set(key, value, expire = null) {
    try {
      const data = {
        value,
        expire: expire ? Date.now() + expire : null
      };
      uni.setStorageSync(key, data);
      return true;
    } catch (error) {
      console.error('存储失败:', error);
      return false;
    }
  },

  /**
   * 获取数据
   * @param {string} key - 存储键名
   * @returns {any} 存储的值
   */
  get(key) {
    try {
      const data = uni.getStorageSync(key);
      
      // 检查是否存在
      if (data === undefined || data === null) {
        return null;
      }
      
      // 检查是否过期
      if (data.expire && Date.now() > data.expire) {
        this.remove(key);
        return null;
      }
      
      return data.value;
    } catch (error) {
      console.error('获取失败:', error);
      return null;
    }
  },

  /**
   * 删除数据
   * @param {string} key - 存储键名
   */
  remove(key) {
    try {
      uni.removeStorageSync(key);
      return true;
    } catch (error) {
      console.error('删除失败:', error);
      return false;
    }
  },

  /**
   * 清除所有数据
   */
  clear() {
    try {
      uni.clearStorageSync();
      return true;
    } catch (error) {
      console.error('清除失败:', error);
      return false;
    }
  },

  /**
   * 获取存储信息
   * @returns {object} 存储信息
   */
  getInfo() {
    try {
      return uni.getStorageInfoSync();
    } catch (error) {
      console.error('获取存储信息失败:', error);
      return null;
    }
  },

  /**
   * 检查是否存在
   * @param {string} key - 存储键名
   * @returns {boolean} 是否存在
   */
  has(key) {
    try {
      const value = this.get(key);
      return value !== null;
    } catch (error) {
      return false;
    }
  }
};

export default storage;

2. 高级缓存工具类

// utils/cache.js

import storage from './storage';

/**
 * 缓存工具类
 */
export const cache = {
  // 缓存键前缀
  prefix: 'app_',
  
  // 缓存版本
  version: '1.0.0',

  /**
   * 生成带前缀的键名
   * @param {string} key - 原始键名
   * @returns {string} 带前缀的键名
   */
  getKey(key) {
    return `${this.prefix}${key}_${this.version}`;
  },

  /**
   * 存储数据
   * @param {string} key - 存储键名
   * @param {any} value - 存储值
   * @param {number} expire - 过期时间(毫秒)
   */
  set(key, value, expire = null) {
    return storage.set(this.getKey(key), value, expire);
  },

  /**
   * 获取数据
   * @param {string} key - 存储键名
   * @returns {any} 存储的值
   */
  get(key) {
    return storage.get(this.getKey(key));
  },

  /**
   * 删除数据
   * @param {string} key - 存储键名
   */
  remove(key) {
    return storage.remove(this.getKey(key));
  },

  /**
   * 清除所有数据
   */
  clear() {
    return storage.clear();
  },

  /**
   * 清除指定前缀的数据
   * @param {string} prefix - 前缀
   */
  clearByPrefix(prefix) {
    try {
      const info = storage.getInfo();
      if (info && info.keys) {
        info.keys.forEach(key => {
          if (key.startsWith(`${this.prefix}${prefix}`)) {
            storage.remove(key);
          }
        });
      }
      return true;
    } catch (error) {
      console.error('清除失败:', error);
      return false;
    }
  },

  /**
   * 缓存网络请求数据
   * @param {string} key - 缓存键名
   * @param {function} requestFn - 请求函数
   * @param {number} expire - 过期时间(毫秒)
   * @returns {Promise} 请求结果
   */
  async cachedRequest(key, requestFn, expire = 5 * 60 * 1000) {
    // 尝试从缓存获取
    const cachedData = this.get(key);
    if (cachedData) {
      return cachedData;
    }

    // 发起请求
    try {
      const data = await requestFn();
      // 缓存数据
      this.set(key, data, expire);
      return data;
    } catch (error) {
      throw error;
    }
  }
};

export default cache;

4. 缓存策略实现

1. 数据缓存策略

// utils/cacheStrategy.js

import cache from './cache';

/**
 * 缓存策略
 */
export const cacheStrategy = {
  /**
   * 缓存配置
   */
  config: {
    // 不同类型数据的过期时间
    expire: {
      // 用户信息:1小时
      user: 60 * 60 * 1000,
      // 商品列表:5分钟
      productList: 5 * 60 * 1000,
      // 商品详情:30分钟
      productDetail: 30 * 60 * 1000,
      // 配置信息:1天
      config: 24 * 60 * 60 * 1000
    }
  },

  /**
   * 缓存用户信息
   * @param {object} userInfo - 用户信息
   */
  cacheUserInfo(userInfo) {
    return cache.set('userInfo', userInfo, this.config.expire.user);
  },

  /**
   * 获取用户信息
   * @returns {object} 用户信息
   */
  getUserInfo() {
    return cache.get('userInfo');
  },

  /**
   * 缓存商品列表
   * @param {array} productList - 商品列表
   * @param {object} params - 请求参数
   */
  cacheProductList(productList, params) {
    const key = `productList_${JSON.stringify(params)}`;
    return cache.set(key, productList, this.config.expire.productList);
  },

  /**
   * 获取商品列表
   * @param {object} params - 请求参数
   * @returns {array} 商品列表
   */
  getProductList(params) {
    const key = `productList_${JSON.stringify(params)}`;
    return cache.get(key);
  },

  /**
   * 缓存商品详情
   * @param {object} productDetail - 商品详情
   * @param {string} productId - 商品ID
   */
  cacheProductDetail(productDetail, productId) {
    const key = `productDetail_${productId}`;
    return cache.set(key, productDetail, this.config.expire.productDetail);
  },

  /**
   * 获取商品详情
   * @param {string} productId - 商品ID
   * @returns {object} 商品详情
   */
  getProductDetail(productId) {
    const key = `productDetail_${productId}`;
    return cache.get(key);
  },

  /**
   * 清除商品相关缓存
   */
  clearProductCache() {
    return cache.clearByPrefix('product');
  },

  /**
   * 清除用户相关缓存
   */
  clearUserCache() {
    return cache.clearByPrefix('user');
  },

  /**
   * 清除所有缓存
   */
  clearAllCache() {
    return cache.clear();
  }
};

export default cacheStrategy;

2. 网络请求缓存

// utils/requestWithCache.js

import request from './request';
import cache from './cache';

/**
 * 带缓存的网络请求
 * @param {string} url - 请求地址
 * @param {object} options - 请求选项
 * @param {number} expire - 缓存过期时间(毫秒)
 * @returns {Promise} 请求结果
 */
export const requestWithCache = async (url, options = {}, expire = 5 * 60 * 1000) => {
  // 生成缓存键
  const cacheKey = `request_${url}_${JSON.stringify(options)}`;
  
  // 尝试从缓存获取
  const cachedData = cache.get(cacheKey);
  if (cachedData) {
    return cachedData;
  }
  
  // 发起请求
  try {
    const data = await request({
      url,
      ...options
    });
    
    // 缓存数据
    cache.set(cacheKey, data, expire);
    
    return data;
  } catch (error) {
    throw error;
  }
};

export default requestWithCache;

5. 缓存管理

1. 缓存监控

// utils/cacheMonitor.js

import storage from './storage';

/**
 * 缓存监控
 */
export const cacheMonitor = {
  /**
   * 检查缓存状态
   */
  checkCacheStatus() {
    try {
      const info = storage.getInfo();
      if (info) {
        console.log('缓存状态:', {
          keys: info.keys.length,
          currentSize: info.currentSize,
          limitSize: info.limitSize,
          usage: ((info.currentSize / info.limitSize) * 100).toFixed(2) + '%'
        });
        
        // 检查缓存是否接近上限
        if (info.currentSize / info.limitSize > 0.8) {
          console.warn('缓存空间即将用完,请考虑清理缓存');
        }
      }
    } catch (error) {
      console.error('检查缓存状态失败:', error);
    }
  },

  /**
   * 清理过期缓存
   */
  clearExpiredCache() {
    try {
      const info = storage.getInfo();
      if (info && info.keys) {
        let clearedCount = 0;
        
        info.keys.forEach(key => {
          try {
            const data = uni.getStorageSync(key);
            if (data && data.expire && Date.now() > data.expire) {
              uni.removeStorageSync(key);
              clearedCount++;
            }
          } catch (error) {
            console.error(`检查缓存项 ${key} 失败:`, error);
          }
        });
        
        console.log(`清理了 ${clearedCount} 个过期缓存`);
      }
    } catch (error) {
      console.error('清理过期缓存失败:', error);
    }
  }
};

export default cacheMonitor;

2. 缓存清理

// utils/cacheCleaner.js

import storage from './storage';
import cache from './cache';

/**
 * 缓存清理器
 */
export const cacheCleaner = {
  /**
   * 清理指定类型的缓存
   * @param {string} type - 缓存类型
   */
  clearCacheByType(type) {
    switch (type) {
      case 'all':
        return this.clearAllCache();
      case 'user':
        return cache.clearByPrefix('user');
      case 'product':
        return cache.clearByPrefix('product');
      case 'image':
        return this.clearImageCache();
      default:
        return false;
    }
  },

  /**
   * 清理所有缓存
   */
  clearAllCache() {
    return cache.clear();
  },

  /**
   * 清理图片缓存
   */
  clearImageCache() {
    // #ifdef APP-PLUS
    uni.clearImageCache({
      success: function() {
        console.log('图片缓存清理成功');
      },
      fail: function() {
        console.error('图片缓存清理失败');
      }
    });
    // #endif
    
    return true;
  },

  /**
   * 获取缓存大小
   * @returns {Promise<number>} 缓存大小(MB)
   */
  getCacheSize() {
    return new Promise((resolve) => {
      // #ifdef APP-PLUS
      plus.cache.calculate((size) => {
        const cacheSize = (size / (1024 * 1024)).toFixed(2);
        resolve(parseFloat(cacheSize));
      });
      // #endif
      
      // #ifdef H5
      const info = storage.getInfo();
      const cacheSize = (info.currentSize / 1024).toFixed(2);
      resolve(parseFloat(cacheSize));
      // #endif
      
      // #ifdef MP-WEIXIN
      const info = storage.getInfo();
      const cacheSize = (info.currentSize / 1024).toFixed(2);
      resolve(parseFloat(cacheSize));
      // #endif
    });
  }
};

export default cacheCleaner;

实用案例分析

案例:实现数据缓存系统

1. 缓存工具类实现

// utils/storage.js

/**
 * 本地存储工具类
 */
export const storage = {
  /**
   * 存储数据
   * @param {string} key - 存储键名
   * @param {any} value - 存储值
   * @param {number} expire - 过期时间(毫秒)
   */
  set(key, value, expire = null) {
    try {
      const data = {
        value,
        expire: expire ? Date.now() + expire : null
      };
      uni.setStorageSync(key, data);
      return true;
    } catch (error) {
      console.error('存储失败:', error);
      return false;
    }
  },

  /**
   * 获取数据
   * @param {string} key - 存储键名
   * @returns {any} 存储的值
   */
  get(key) {
    try {
      const data = uni.getStorageSync(key);
      
      // 检查是否存在
      if (data === undefined || data === null) {
        return null;
      }
      
      // 检查是否过期
      if (data.expire && Date.now() > data.expire) {
        this.remove(key);
        return null;
      }
      
      return data.value;
    } catch (error) {
      console.error('获取失败:', error);
      return null;
    }
  },

  /**
   * 删除数据
   * @param {string} key - 存储键名
   */
  remove(key) {
    try {
      uni.removeStorageSync(key);
      return true;
    } catch (error) {
      console.error('删除失败:', error);
      return false;
    }
  },

  /**
   * 清除所有数据
   */
  clear() {
    try {
      uni.clearStorageSync();
      return true;
    } catch (error) {
      console.error('清除失败:', error);
      return false;
    }
  },

  /**
   * 获取存储信息
   * @returns {object} 存储信息
   */
  getInfo() {
    try {
      return uni.getStorageInfoSync();
    } catch (error) {
      console.error('获取存储信息失败:', error);
      return null;
    }
  },

  /**
   * 检查是否存在
   * @param {string} key - 存储键名
   * @returns {boolean} 是否存在
   */
  has(key) {
    try {
      const value = this.get(key);
      return value !== null;
    } catch (error) {
      return false;
    }
  }
};

export default storage;
// utils/cache.js

import storage from './storage';

/**
 * 缓存工具类
 */
export const cache = {
  // 缓存键前缀
  prefix: 'app_',
  
  // 缓存版本
  version: '1.0.0',

  /**
   * 生成带前缀的键名
   * @param {string} key - 原始键名
   * @returns {string} 带前缀的键名
   */
  getKey(key) {
    return `${this.prefix}${key}_${this.version}`;
  },

  /**
   * 存储数据
   * @param {string} key - 存储键名
   * @param {any} value - 存储值
   * @param {number} expire - 过期时间(毫秒)
   */
  set(key, value, expire = null) {
    return storage.set(this.getKey(key), value, expire);
  },

  /**
   * 获取数据
   * @param {string} key - 存储键名
   * @returns {any} 存储的值
   */
  get(key) {
    return storage.get(this.getKey(key));
  },

  /**
   * 删除数据
   * @param {string} key - 存储键名
   */
  remove(key) {
    return storage.remove(this.getKey(key));
  },

  /**
   * 清除所有数据
   */
  clear() {
    return storage.clear();
  },

  /**
   * 清除指定前缀的数据
   * @param {string} prefix - 前缀
   */
  clearByPrefix(prefix) {
    try {
      const info = storage.getInfo();
      if (info && info.keys) {
        info.keys.forEach(key => {
          if (key.startsWith(`${this.prefix}${prefix}`)) {
            storage.remove(key);
          }
        });
      }
      return true;
    } catch (error) {
      console.error('清除失败:', error);
      return false;
    }
  },

  /**
   * 缓存网络请求数据
   * @param {string} key - 缓存键名
   * @param {function} requestFn - 请求函数
   * @param {number} expire - 过期时间(毫秒)
   * @returns {Promise} 请求结果
   */
  async cachedRequest(key, requestFn, expire = 5 * 60 * 1000) {
    // 尝试从缓存获取
    const cachedData = this.get(key);
    if (cachedData) {
      return cachedData;
    }

    // 发起请求
    try {
      const data = await requestFn();
      // 缓存数据
      this.set(key, data, expire);
      return data;
    } catch (error) {
      throw error;
    }
  }
};

export default cache;

2. 缓存策略实现

// utils/cacheStrategy.js

import cache from './cache';

/**
 * 缓存策略
 */
export const cacheStrategy = {
  /**
   * 缓存配置
   */
  config: {
    // 不同类型数据的过期时间
    expire: {
      // 用户信息:1小时
      user: 60 * 60 * 1000,
      // 商品列表:5分钟
      productList: 5 * 60 * 1000,
      // 商品详情:30分钟
      productDetail: 30 * 60 * 1000,
      // 配置信息:1天
      config: 24 * 60 * 60 * 1000
    }
  },

  /**
   * 缓存用户信息
   * @param {object} userInfo - 用户信息
   */
  cacheUserInfo(userInfo) {
    return cache.set('userInfo', userInfo, this.config.expire.user);
  },

  /**
   * 获取用户信息
   * @returns {object} 用户信息
   */
  getUserInfo() {
    return cache.get('userInfo');
  },

  /**
   * 缓存商品列表
   * @param {array} productList - 商品列表
   * @param {object} params - 请求参数
   */
  cacheProductList(productList, params) {
    const key = `productList_${JSON.stringify(params)}`;
    return cache.set(key, productList, this.config.expire.productList);
  },

  /**
   * 获取商品列表
   * @param {object} params - 请求参数
   * @returns {array} 商品列表
   */
  getProductList(params) {
    const key = `productList_${JSON.stringify(params)}`;
    return cache.get(key);
  },

  /**
   * 缓存商品详情
   * @param {object} productDetail - 商品详情
   * @param {string} productId - 商品ID
   */
  cacheProductDetail(productDetail, productId) {
    const key = `productDetail_${productId}`;
    return cache.set(key, productDetail, this.config.expire.productDetail);
  },

  /**
   * 获取商品详情
   * @param {string} productId - 商品ID
   * @returns {object} 商品详情
   */
  getProductDetail(productId) {
    const key = `productDetail_${productId}`;
    return cache.get(key);
  },

  /**
   * 清除商品相关缓存
   */
  clearProductCache() {
    return cache.clearByPrefix('product');
  },

  /**
   * 清除用户相关缓存
   */
  clearUserCache() {
    return cache.clearByPrefix('user');
  },

  /**
   * 清除所有缓存
   */
  clearAllCache() {
    return cache.clear();
  }
};

export default cacheStrategy;

3. 缓存管理实现

// utils/cacheManager.js

import cache from './cache';
import storage from './storage';

/**
 * 缓存管理器
 */
export const cacheManager = {
  /**
   * 初始化缓存
   */
  init() {
    // 检查缓存版本
    this.checkVersion();
    
    // 清理过期缓存
    this.cleanExpiredCache();
  },

  /**
   * 检查缓存版本
   */
  checkVersion() {
    const currentVersion = cache.version;
    const cachedVersion = storage.get('appVersion');
    
    if (cachedVersion !== currentVersion) {
      // 版本更新,清理旧缓存
      console.log('缓存版本更新,清理旧缓存');
      cache.clear();
      storage.set('appVersion', currentVersion);
    }
  },

  /**
   * 清理过期缓存
   */
  cleanExpiredCache() {
    try {
      const info = storage.getInfo();
      if (info && info.keys) {
        let clearedCount = 0;
        
        info.keys.forEach(key => {
          try {
            const data = storage.get(key);
            if (data === null) {
              storage.remove(key);
              clearedCount++;
            }
          } catch (error) {
            console.error(`检查缓存项 ${key} 失败:`, error);
          }
        });
        
        if (clearedCount > 0) {
          console.log(`清理了 ${clearedCount} 个过期缓存`);
        }
      }
    } catch (error) {
      console.error('清理过期缓存失败:', error);
    }
  },

  /**
   * 监控缓存状态
   */
  monitorCacheStatus() {
    const info = storage.getInfo();
    if (info) {
      const usage = (info.currentSize / info.limitSize) * 100;
      console.log(`缓存使用情况: ${info.currentSize}KB / ${info.limitSize}KB (${usage.toFixed(2)}%)`);
      
      if (usage > 80) {
        console.warn('缓存空间即将用完,请考虑清理缓存');
      }
    }
  }
};

export default cacheManager;

4. 实际应用示例

<!-- pages/product/list.vue -->
<template>
  <view class="container">
    <view class="product-list">
      <view 
        v-for="product in productList" 
        :key="product.id"
        class="product-item"
        @click="navigateToDetail(product.id)"
      >
        <image :src="product.image" class="product-image"></image>
        <view class="product-info">
          <text class="product-name">{{ product.name }}</text>
          <text class="product-price">¥{{ product.price }}</text>
        </view>
      </view>
    </view>
  </view>
</template>

<script>
import api from '@/api';
import { cacheStrategy } from '@/utils/cacheStrategy';
import { cacheManager } from '@/utils/cacheManager';

export default {
  data() {
    return {
      productList: [],
      page: 1,
      pageSize: 10
    };
  },
  onLoad() {
    // 初始化缓存管理器
    cacheManager.init();
    
    // 监控缓存状态
    cacheManager.monitorCacheStatus();
    
    this.getProductList();
  },
  methods: {
    async getProductList() {
      const params = {
        page: this.page,
        pageSize: this.pageSize
      };
      
      // 尝试从缓存获取
      const cachedList = cacheStrategy.getProductList(params);
      if (cachedList) {
        console.log('从缓存获取商品列表');
        this.productList = cachedList;
        return;
      }
      
      // 从服务器获取
      try {
        console.log('从服务器获取商品列表');
        const res = await api.product.getProductList(params);
        this.productList = res.data.list;
        
        // 缓存数据
        cacheStrategy.cacheProductList(this.productList, params);
      } catch (error) {
        console.error('获取商品列表失败:', error);
      }
    },
    
    navigateToDetail(productId) {
      uni.navigateTo({
        url: `/pages/product/detail?id=${productId}`
      });
    }
  }
};
</script>

<style scoped>
.container {
  flex: 1;
  padding: 20rpx;
  background-color: #f5f5f5;
}

.product-list {
  display: flex;
  flex-direction: column;
  gap: 20rpx;
}

.product-item {
  display: flex;
  padding: 20rpx;
  background-color: #fff;
  border-radius: 10rpx;
  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
}

.product-image {
  width: 160rpx;
  height: 160rpx;
  border-radius: 10rpx;
  margin-right: 20rpx;
}

.product-info {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

.product-name {
  font-size: 28rpx;
  color: #333;
  margin-bottom: 10rpx;
}

.product-price {
  font-size: 32rpx;
  color: #ff4d4f;
  font-weight: bold;
}
</style>
<!-- pages/product/detail.vue -->
<template>
  <view class="container">
    <view v-if="productDetail">
      <image :src="productDetail.image" class="product-image"></image>
      <view class="product-info">
        <text class="product-name">{{ productDetail.name }}</text>
        <text class="product-price">¥{{ productDetail.price }}</text>
        <text class="product-desc">{{ productDetail.description }}</text>
      </view>
    </view>
    <view v-else class="loading">加载中...</view>
  </view>
</template>

<script>
import api from '@/api';
import { cacheStrategy } from '@/utils/cacheStrategy';

export default {
  data() {
    return {
      productDetail: null,
      productId: ''
    };
  },
  onLoad(options) {
    this.productId = options.id;
    this.getProductDetail();
  },
  methods: {
    async getProductDetail() {
      // 尝试从缓存获取
      const cachedDetail = cacheStrategy.getProductDetail(this.productId);
      if (cachedDetail) {
        console.log('从缓存获取商品详情');
        this.productDetail = cachedDetail;
        return;
      }
      
      // 从服务器获取
      try {
        console.log('从服务器获取商品详情');
        const res = await api.product.getProductDetail(this.productId);
        this.productDetail = res.data;
        
        // 缓存数据
        cacheStrategy.cacheProductDetail(this.productDetail, this.productId);
      } catch (error) {
        console.error('获取商品详情失败:', error);
      }
    }
  }
};
</script>

<style scoped>
.container {
  flex: 1;
  padding: 20rpx;
  background-color: #f5f5f5;
}

.product-image {
  width: 100%;
  height: 400rpx;
  border-radius: 10rpx;
  margin-bottom: 20rpx;
}

.product-info {
  background-color: #fff;
  padding: 20rpx;
  border-radius: 10rpx;
  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
}

.product-name {
  font-size: 32rpx;
  font-weight: bold;
  color: #333;
  margin-bottom: 10rpx;
  display: block;
}

.product-price {
  font-size: 36rpx;
  color: #ff4d4f;
  font-weight: bold;
  margin-bottom: 20rpx;
  display: block;
}

.product-desc {
  font-size: 24rpx;
  color: #666;
  line-height: 1.5;
}

.loading {
  text-align: center;
  padding: 100rpx 0;
  color: #999;
}
</style>
<!-- pages/settings/settings.vue -->
<template>
  <view class="container">
    <view class="setting-item" @click="handleClearCache">
      <text class="setting-label">清除缓存</text>
      <view class="setting-right">
        <text class="cache-size">{{ cacheSize }}MB</text>
        <text class="arrow">></text>
      </view>
    </view>
    
    <view class="setting-item" @click="handleClearTypeCache">
      <text class="setting-label">清除类型缓存</text>
      <text class="arrow">></text>
    </view>
  </view>
</template>

<script>
import { cacheCleaner } from '@/utils/cacheCleaner';

export default {
  data() {
    return {
      cacheSize: 0
    };
  },
  onLoad() {
    this.getCacheSize();
  },
  methods: {
    async getCacheSize() {
      const size = await cacheCleaner.getCacheSize();
      this.cacheSize = size;
    },
    
    handleClearCache() {
      uni.showModal({
        title: '清除缓存',
        content: '确定要清除所有缓存吗?',
        success: (res) => {
          if (res.confirm) {
            cacheCleaner.clearAllCache();
            this.getCacheSize();
            uni.showToast({
              title: '缓存清除成功',
              icon: 'success'
            });
          }
        }
      });
    },
    
    handleClearTypeCache() {
      uni.showActionSheet({
        itemList: ['清除用户缓存', '清除商品缓存', '清除图片缓存'],
        success: (res) => {
          let type = '';
          switch (res.tapIndex) {
            case 0:
              type = 'user';
              break;
            case 1:
              type = 'product';
              break;
            case 2:
              type = 'image';
              break;
          }
          
          cacheCleaner.clearCacheByType(type);
          this.getCacheSize();
          uni.showToast({
            title: '缓存清除成功',
            icon: 'success'
          });
        }
      });
    }
  }
};
</script>

<style scoped>
.container {
  flex: 1;
  background-color: #f5f5f5;
}

.setting-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 30rpx 20rpx;
  background-color: #fff;
  margin-bottom: 1rpx;
}

.setting-label {
  font-size: 28rpx;
  color: #333;
}

.setting-right {
  display: flex;
  align-items: center;
}

.cache-size {
  font-size: 24rpx;
  color: #999;
  margin-right: 10rpx;
}

.arrow {
  font-size: 24rpx;
  color: #999;
}
</style>

常见问题与解决方案

1. 缓存空间不足

问题:本地缓存空间不足,导致无法存储新数据。

解决方案

  • 定期清理过期缓存
  • 对不同类型的数据设置合理的过期时间
  • 监控缓存使用情况,在接近上限时提醒用户清理
  • 使用版本化缓存,在应用版本更新时清理旧缓存

2. 缓存数据过期

问题:缓存数据过期,导致显示旧数据或错误数据。

解决方案

  • 为缓存数据设置合理的过期时间
  • 在获取缓存数据时检查是否过期
  • 实现缓存更新机制,确保数据及时更新
  • 对于重要数据,每次都从服务器获取并更新缓存

3. 缓存一致性

问题:缓存数据与服务器数据不一致,导致显示错误信息。

解决方案

  • 实现缓存更新策略,如轮询、WebSocket 推送等
  • 在数据发生变化时主动更新缓存
  • 对于关键操作,先更新服务器数据,再更新缓存
  • 使用乐观锁或悲观锁确保数据一致性

4. 缓存清理不彻底

问题:缓存清理后,仍有部分缓存残留。

解决方案

  • 实现全面的缓存清理功能,包括本地存储、图片缓存等
  • 针对不同类型的缓存采用不同的清理策略
  • 提供缓存清理进度和结果反馈
  • 在应用版本更新时强制清理所有旧缓存

5. 缓存性能问题

问题:缓存操作影响应用性能,如存储大量数据时卡顿。

解决方案

  • 对于大量数据,使用异步存储 API
  • 实现缓存批处理,减少频繁的存储操作
  • 对缓存数据进行压缩,减少存储空间和读写时间
  • 使用索引和分页技术,提高缓存查询效率

代码优化建议

1. 优化存储操作

// 优化前:频繁调用存储 API
for (let i = 0; i < 100; i++) {
  uni.setStorageSync(`key_${i}`, i);
}

// 优化后:批量存储
const data = {};
for (let i = 0; i < 100; i++) {
  data[`key_${i}`] = i;
}
uni.setStorageSync('batchData', data);

2. 优化缓存键生成

// 优化前:复杂的键名生成
const key = `productList_${params.page}_${params.pageSize}_${params.category}`;

// 优化后:使用 JSON.stringify 生成键名
const key = `productList_${JSON.stringify(params)}`;

3. 优化缓存检查

// 优化前:每次都检查缓存
async function getData(params) {
  const cachedData = cache.get(`data_${JSON.stringify(params)}`);
  if (cachedData) {
    return cachedData;
  }
  
  const data = await fetchData(params);
  cache.set(`data_${JSON.stringify(params)}`, data);
  return data;
}

// 优化后:使用缓存请求封装
async function getData(params) {
  return cache.cachedRequest(`data_${JSON.stringify(params)}`, () => fetchData(params));
}

4. 优化缓存策略

// 优化前:所有数据使用相同的过期时间
cache.set('productList', data, 5 * 60 * 1000);
cache.set('userInfo', data, 5 * 60 * 1000);

// 优化后:根据数据类型设置不同的过期时间
cache.set('productList', data, 5 * 60 * 1000);
cache.set('userInfo', data, 60 * 60 * 1000);

5. 优化缓存清理

// 优化前:手动清理每个缓存项
uni.removeStorageSync('userInfo');
uni.removeStorageSync('productList');
uni.removeStorageSync('productDetail');

// 优化后:按前缀清理
cache.clearByPrefix('user');
cache.clearByPrefix('product');

章节总结

本章节详细介绍了 uni-app 中的数据缓存策略,包括:

  1. 本地存储 API:介绍了 uni-app 提供的本地存储 API,如 setStorage、getStorage、removeStorage 等
  2. 缓存工具类:封装了基础缓存工具类和高级缓存工具类,提供更方便的缓存操作
  3. 缓存策略:实现了不同类型数据的缓存策略,包括过期时间设置、缓存键生成等
  4. 缓存管理:实现了缓存监控、缓存清理等功能,确保缓存系统的正常运行
  5. 实用案例:通过商品列表、商品详情和设置页面的示例,展示了缓存系统的实际应用
  6. 常见问题与解决方案:针对缓存空间不足、数据过期、一致性等问题提供了解决方案
  7. 代码优化建议:提供了缓存操作、键生成、检查和清理等方面的优化建议

通过本章节的学习,你应该能够掌握 uni-app 中的数据缓存策略,构建高效、可靠的缓存系统,提升应用性能和用户体验。在实际开发中,应根据应用的具体需求,选择合适的缓存策略和工具,确保数据的准确性和系统的稳定性。

« 上一篇 uni-app 网络请求封装 下一篇 » uni-app 国际化